diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts index 4fde32890c..749a8dda7a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts @@ -41,6 +41,7 @@ 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'; import { FlowConfigurationEffects } from './state/flow-configuration/flow-configuration.effects'; +import { ComponentStateEffects } from './state/component-state/component-state.effects'; @NgModule({ declarations: [AppComponent], @@ -65,7 +66,8 @@ import { FlowConfigurationEffects } from './state/flow-configuration/flow-config FlowConfigurationEffects, StatusHistoryEffects, ControllerServiceStateEffects, - SystemDiagnosticsEffects + SystemDiagnosticsEffects, + ComponentStateEffects ), StoreDevtoolsModule.instrument({ maxAge: 25, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts index 1210caef12..5e27e1cab8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts @@ -55,6 +55,7 @@ import { ContextMenuItemDefinition } from '../../../ui/common/context-menu/context-menu.component'; import { promptEmptyQueueRequest, promptEmptyQueuesRequest } from '../state/queue/queue.actions'; +import { getComponentStateAndOpenDialog } from '../../../state/component-state/component-state.actions'; @Injectable({ providedIn: 'root' }) export class CanvasContextMenu implements ContextMenuDefinitionProvider { @@ -679,13 +680,21 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { }, { condition: (selection: any) => { - // TODO - isStatefulProcessor - return false; + return this.canvasUtils.isStatefulProcessor(selection); }, clazz: 'fa fa-tasks', text: 'View state', - action: () => { - // TODO - viewState + action: (selection: any) => { + const selectionData = selection.datum(); + this.store.dispatch( + getComponentStateAndOpenDialog({ + request: { + componentName: selectionData.component.name, + componentUri: selectionData.uri, + canClear: this.canvasUtils.isConfigurable(selection) + } + }) + ); } }, { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts index 4e51520609..1f13e28667 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts @@ -464,6 +464,28 @@ export class CanvasUtils { return selection.size() === 1 && selection.classed('funnel'); } + /** + * Determines whether the current selection is a stateful processor. + * + * @param {selection} selection + */ + public isStatefulProcessor(selection: any): boolean { + // ensure the correct number of components are selected + if (selection.size() !== 1) { + return false; + } + if (this.canRead(selection) === false || this.canModify(selection) === false) { + return false; + } + + if (this.isProcessor(selection)) { + const processorData: any = selection.datum(); + return processorData.component.persistsState === true; + } else { + return false; + } + } + /** * Determines whether the user can configure or open the policy management page. */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html index b950e7567c..0b3b547be5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html @@ -48,6 +48,7 @@ (configureControllerService)="configureControllerService($event)" (enableControllerService)="enableControllerService($event)" (disableControllerService)="disableControllerService($event)" + (viewStateControllerService)="viewStateControllerService($event)" (deleteControllerService)="deleteControllerService($event)">
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts index 435846b74a..34cf6502be 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts @@ -21,15 +21,23 @@ import { ControllerServices } from './controller-services.component'; import { provideMockStore } from '@ngrx/store/testing'; import { initialState } from '../../state/controller-services/controller-services.reducer'; import { RouterTestingModule } from '@angular/router/testing'; +import { Component } from '@angular/core'; describe('ControllerServices', () => { let component: ControllerServices; let fixture: ComponentFixture; + @Component({ + selector: 'navigation', + standalone: true, + template: '' + }) + class MockNavigation {} + beforeEach(() => { TestBed.configureTestingModule({ declarations: [ControllerServices], - imports: [RouterTestingModule], + imports: [RouterTestingModule, MockNavigation], providers: [ provideMockStore({ initialState diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts index 0f2b78db14..6117b2894c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts @@ -45,6 +45,7 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors'; import { NiFiState } from '../../../../state'; import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions'; +import { getComponentStateAndOpenDialog } from '../../../../state/component-state/component-state.actions'; @Component({ selector: 'controller-services', @@ -189,6 +190,18 @@ export class ControllerServices implements OnInit, OnDestroy { ); } + viewStateControllerService(entity: ControllerServiceEntity): void { + this.store.dispatch( + getComponentStateAndOpenDialog({ + request: { + componentUri: entity.uri, + componentName: entity.component.name, + canClear: entity.component.state === 'DISABLED' + } + }) + ); + } + deleteControllerService(entity: ControllerServiceEntity): void { this.store.dispatch( promptControllerServiceDeletion({ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html index 32802aa136..1b0d10065a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html @@ -134,7 +134,11 @@ *ngIf="canDelete(item)" (click)="deleteClicked(item)" title="Delete">
-
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts index 5108c5d352..16869ec6ef 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts @@ -60,6 +60,8 @@ export class FlowAnalysisRuleTable { @Output() configureFlowAnalysisRule: EventEmitter = new EventEmitter(); @Output() enableFlowAnalysisRule: EventEmitter = new EventEmitter(); + @Output() viewStateFlowAnalysisRule: EventEmitter = + new EventEmitter(); @Output() disableFlowAnalysisRule: EventEmitter = new EventEmitter(); @@ -255,6 +257,10 @@ export class FlowAnalysisRuleTable { return this.canRead(entity) && this.canWrite(entity) && entity.component.persistsState === true; } + viewStateClicked(entity: FlowAnalysisRuleEntity): void { + this.viewStateFlowAnalysisRule.next(entity); + } + select(entity: FlowAnalysisRuleEntity): void { this.selectFlowAnalysisRule.next(entity); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html index 219b7dd22e..0c1b2e96bf 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html @@ -35,6 +35,7 @@ (selectFlowAnalysisRule)="selectFlowAnalysisRule($event)" (enableFlowAnalysisRule)="enableFlowAnalysisRule($event)" (disableFlowAnalysisRule)="disableFlowAnalysisRule($event)" + (viewStateFlowAnalysisRule)="viewStateFlowAnalysisRule($event)" (deleteFlowAnalysisRule)="deleteFlowAnalysisRule($event)">
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts index 42f882ceba..c1596d7295 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts @@ -41,6 +41,7 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s import { NiFiState } from '../../../../state'; import { FlowAnalysisRuleEntity, FlowAnalysisRulesState } from '../../state/flow-analysis-rules'; import { CurrentUser } from '../../../../state/current-user'; +import { getComponentStateAndOpenDialog } from '../../../../state/component-state/component-state.actions'; @Component({ selector: 'flow-analysis-rules', @@ -128,6 +129,19 @@ export class FlowAnalysisRules implements OnInit, OnDestroy { ); } + viewStateFlowAnalysisRule(entity: FlowAnalysisRuleEntity): void { + const canClear: boolean = entity.status.runStatus === 'DISABLED'; + this.store.dispatch( + getComponentStateAndOpenDialog({ + request: { + componentUri: entity.uri, + componentName: entity.component.name, + canClear + } + }) + ); + } + deleteFlowAnalysisRule(entity: FlowAnalysisRuleEntity): void { this.store.dispatch( promptFlowAnalysisRuleDeletion({ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html index 9545cb37ca..42de3d7de0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html @@ -39,6 +39,7 @@ (configureControllerService)="configureControllerService($event)" (enableControllerService)="enableControllerService($event)" (disableControllerService)="disableControllerService($event)" + (viewStateControllerService)="viewStateControllerService($event)" (deleteControllerService)="deleteControllerService($event)">
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts index 403bef96f7..7635b648cb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts @@ -44,6 +44,7 @@ import { NiFiState } from '../../../../state'; import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors'; import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions'; import { CurrentUser } from '../../../../state/current-user'; +import { getComponentStateAndOpenDialog } from '../../../../state/component-state/component-state.actions'; @Component({ selector: 'management-controller-services', @@ -139,6 +140,18 @@ export class ManagementControllerServices implements OnInit, OnDestroy { ); } + viewStateControllerService(entity: ControllerServiceEntity): void { + this.store.dispatch( + getComponentStateAndOpenDialog({ + request: { + componentUri: entity.uri, + componentName: entity.component.name, + canClear: entity.component.state === 'DISABLED' + } + }) + ); + } + deleteControllerService(entity: ControllerServiceEntity): void { this.store.dispatch( promptControllerServiceDeletion({ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html index c0265f5d1b..ca78eaeaa7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html @@ -124,7 +124,11 @@ *ngIf="canDelete(item)" (click)="deleteClicked(item)" title="Delete">
-
+
= new EventEmitter(); @Output() startReportingTask: EventEmitter = new EventEmitter(); @Output() configureReportingTask: EventEmitter = new EventEmitter(); + @Output() viewStateReportingTask: EventEmitter = new EventEmitter(); @Output() stopReportingTask: EventEmitter = new EventEmitter(); protected readonly TextTip = TextTip; @@ -233,6 +234,10 @@ export class ReportingTaskTable { return this.canRead(entity) && this.canWrite(entity) && entity.component.persistsState === true; } + viewStateClicked(entity: ReportingTaskEntity): void { + this.viewStateReportingTask.next(entity); + } + canManageAccessPolicies(): boolean { return this.flowConfiguration.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html index 771e6bc830..3414c21b1d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html @@ -33,6 +33,7 @@ [currentUser]="currentUser" [flowConfiguration]="(flowConfiguration$ | async)!" (configureReportingTask)="configureReportingTask($event)" + (viewStateReportingTask)="viewStateReportingTask($event)" (selectReportingTask)="selectReportingTask($event)" (deleteReportingTask)="deleteReportingTask($event)" (stopReportingTask)="stopReportingTask($event)" diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts index c35e36fee8..fd8c6471cf 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts @@ -42,6 +42,7 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s import { NiFiState } from '../../../../state'; import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions'; import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors'; +import { getComponentStateAndOpenDialog } from '../../../../state/component-state/component-state.actions'; @Component({ selector: 'reporting-tasks', @@ -127,6 +128,19 @@ export class ReportingTasks implements OnInit, OnDestroy { ); } + viewStateReportingTask(entity: ReportingTaskEntity): void { + const canClear: boolean = entity.status.runStatus === 'STOPPED' && entity.status.activeThreadCount === 0; + this.store.dispatch( + getComponentStateAndOpenDialog({ + request: { + componentUri: entity.uri, + componentName: entity.component.name, + canClear + } + }) + ); + } + startReportingTask(entity: ReportingTaskEntity): void { this.store.dispatch( startReportingTask({ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/component-state.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/component-state.service.ts new file mode 100644 index 0000000000..f87d93b7b4 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/component-state.service.ts @@ -0,0 +1,51 @@ +/* + * 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 { ClearComponentStateRequest, LoadComponentStateRequest } from '../state/component-state'; +import { Observable } from 'rxjs'; +import { NiFiCommon } from './nifi-common.service'; + +@Injectable({ providedIn: 'root' }) +export class ComponentStateService { + constructor( + private httpClient: HttpClient, + private nifiCommon: NiFiCommon + ) {} + + /** + * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling + * does not work on absolute URLs, so we need to strip off the proto for the request header to be added. + * + * https://stackoverflow.com/a/59586462 + * + * @param url + * @private + */ + private stripProtocol(url: string): string { + return this.nifiCommon.substringAfterFirst(url, ':'); + } + + getComponentState(request: LoadComponentStateRequest): Observable { + return this.httpClient.get(`${this.stripProtocol(request.componentUri)}/state`); + } + + clearComponentState(request: ClearComponentStateRequest): Observable { + return this.httpClient.post(`${this.stripProtocol(request.componentUri)}/state/clear-requests`, {}); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.actions.ts new file mode 100644 index 0000000000..bb2ee31219 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.actions.ts @@ -0,0 +1,49 @@ +/* + * 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 { ComponentStateRequest, ComponentStateResponse } from './index'; + +const COMPONENT_STATE_PREFIX = '[Component State]'; + +export const getComponentStateAndOpenDialog = createAction( + `${COMPONENT_STATE_PREFIX} Get Component State and Open Dialog`, + props<{ request: ComponentStateRequest }>() +); + +export const loadComponentStateSuccess = createAction( + `${COMPONENT_STATE_PREFIX} Load Component State Success`, + props<{ response: ComponentStateResponse }>() +); + +export const openComponentStateDialog = createAction(`${COMPONENT_STATE_PREFIX} Open Component State Dialog`); + +export const componentStateApiError = createAction( + `${COMPONENT_STATE_PREFIX} Component State API error`, + props<{ error: string }>() +); + +export const clearComponentState = createAction(`${COMPONENT_STATE_PREFIX} Clear Component State`); + +export const reloadComponentState = createAction(`${COMPONENT_STATE_PREFIX} Reload Component State`); + +export const reloadComponentStateSuccess = createAction( + `${COMPONENT_STATE_PREFIX} Reload Component State Success`, + props<{ response: ComponentStateResponse }>() +); + +export const resetComponentState = createAction(`${COMPONENT_STATE_PREFIX} Reset Component State`); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.effects.ts new file mode 100644 index 0000000000..5f02df85a2 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.effects.ts @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { NiFiState } from '../index'; +import * as ComponentStateActions from './component-state.actions'; +import { catchError, from, map, of, switchMap, tap } from 'rxjs'; +import { MatDialog } from '@angular/material/dialog'; +import { ComponentStateService } from '../../service/component-state.service'; +import { ComponentStateDialog } from '../../ui/common/component-state/component-state.component'; +import { resetComponentState } from './component-state.actions'; +import { selectComponentUri } from './component-state.selectors'; +import { isDefinedAndNotNull } from '../shared'; + +@Injectable() +export class ComponentStateEffects { + constructor( + private actions$: Actions, + private store: Store, + private componentStateService: ComponentStateService, + private dialog: MatDialog + ) {} + + getComponentStateAndOpenDialog$ = createEffect(() => + this.actions$.pipe( + ofType(ComponentStateActions.getComponentStateAndOpenDialog), + map((action) => action.request), + switchMap((request) => + from( + this.componentStateService.getComponentState({ componentUri: request.componentUri }).pipe( + map((response: any) => + ComponentStateActions.loadComponentStateSuccess({ + response: { + componentState: response.componentState + } + }) + ), + catchError((error) => + of( + ComponentStateActions.componentStateApiError({ + error: error.error + }) + ) + ) + ) + ) + ) + ) + ); + + loadComponentStateSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(ComponentStateActions.loadComponentStateSuccess), + map((action) => action.response), + switchMap((response) => of(ComponentStateActions.openComponentStateDialog())) + ) + ); + + openComponentStateDialog$ = createEffect( + () => + this.actions$.pipe( + ofType(ComponentStateActions.openComponentStateDialog), + tap(() => { + const dialogReference = this.dialog.open(ComponentStateDialog, { + panelClass: 'large-dialog' + }); + + dialogReference.afterClosed().subscribe((response) => { + this.store.dispatch(resetComponentState()); + }); + }) + ), + { dispatch: false } + ); + + clearComponentState$ = createEffect(() => + this.actions$.pipe( + ofType(ComponentStateActions.clearComponentState), + concatLatestFrom(() => this.store.select(selectComponentUri).pipe(isDefinedAndNotNull())), + switchMap(([action, componentUri]) => + from( + this.componentStateService.clearComponentState({ componentUri }).pipe( + map((response: any) => ComponentStateActions.reloadComponentState()), + catchError((error) => + of( + ComponentStateActions.componentStateApiError({ + error: error.error + }) + ) + ) + ) + ) + ) + ) + ); + + reloadComponentState$ = createEffect(() => + this.actions$.pipe( + ofType(ComponentStateActions.reloadComponentState), + concatLatestFrom(() => this.store.select(selectComponentUri).pipe(isDefinedAndNotNull())), + switchMap(([action, componentUri]) => + from( + this.componentStateService.getComponentState({ componentUri }).pipe( + map((response: any) => + ComponentStateActions.reloadComponentStateSuccess({ + response: { + componentState: response.componentState + } + }) + ), + catchError((error) => + of( + ComponentStateActions.componentStateApiError({ + error: error.error + }) + ) + ) + ) + ) + ) + ) + ); +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.reducer.ts new file mode 100644 index 0000000000..d8ee39c266 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.reducer.ts @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentStateState } from './index'; +import { createReducer, on } from '@ngrx/store'; +import { + resetComponentState, + loadComponentStateSuccess, + componentStateApiError, + getComponentStateAndOpenDialog, + reloadComponentStateSuccess +} from './component-state.actions'; + +export const initialState: ComponentStateState = { + componentName: null, + componentUri: null, + componentState: null, + canClear: null, + status: 'pending', + error: null +}; + +export const componentStateReducer = createReducer( + initialState, + on(getComponentStateAndOpenDialog, (state, { request }) => ({ + ...state, + componentName: request.componentName, + componentUri: request.componentUri, + canClear: request.canClear, + status: 'loading' as const + })), + on(loadComponentStateSuccess, reloadComponentStateSuccess, (state, { response }) => ({ + ...state, + error: null, + status: 'success' as const, + componentState: response.componentState + })), + on(componentStateApiError, (state, { error }) => ({ + ...state, + error, + status: 'error' as const + })), + on(resetComponentState, (state) => ({ + ...initialState + })) +); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.selectors.ts new file mode 100644 index 0000000000..e418e0d8a9 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.selectors.ts @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createFeatureSelector, createSelector } from '@ngrx/store'; +import { componentStateFeatureKey, ComponentStateState } from './index'; + +export const selectComponentStateState = createFeatureSelector(componentStateFeatureKey); + +export const selectComponentState = createSelector( + selectComponentStateState, + (state: ComponentStateState) => state.componentState +); + +export const selectComponentName = createSelector( + selectComponentStateState, + (state: ComponentStateState) => state.componentName +); + +export const selectComponentUri = createSelector( + selectComponentStateState, + (state: ComponentStateState) => state.componentUri +); + +export const selectCanClear = createSelector(selectComponentStateState, (state: ComponentStateState) => state.canClear); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/index.ts new file mode 100644 index 0000000000..7335e99fba --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/index.ts @@ -0,0 +1,65 @@ +/* + * 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 componentStateFeatureKey = 'componentState'; + +export interface ComponentStateRequest { + componentName: string; + componentUri: string; + canClear: boolean; +} + +export interface LoadComponentStateRequest { + componentUri: string; +} + +export interface ClearComponentStateRequest { + componentUri: string; +} + +export interface ComponentStateResponse { + componentState: ComponentState; +} + +export interface StateEntry { + key: string; + value: string; + clusterNodeId?: string; + clusterNodeAddress?: string; +} + +export interface StateMap { + scope: string; + state: StateEntry[]; + totalEntryCount: number; +} + +export interface ComponentState { + componentId: string; + localState?: StateMap; + clusterState?: StateMap; + stateDescription: string; +} + +export interface ComponentStateState { + componentName: string | null; + componentUri: string | null; + componentState: ComponentState | null; + canClear: boolean | null; + error: string | null; + status: 'pending' | 'loading' | 'error' | 'success'; +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts index e4b7452e89..4ae6619448 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts @@ -31,6 +31,8 @@ import { systemDiagnosticsFeatureKey, SystemDiagnosticsState } from './system-di import { systemDiagnosticsReducer } from './system-diagnostics/system-diagnostics.reducer'; import { flowConfigurationFeatureKey, FlowConfigurationState } from './flow-configuration'; import { flowConfigurationReducer } from './flow-configuration/flow-configuration.reducer'; +import { componentStateFeatureKey, ComponentStateState } from './component-state'; +import { componentStateReducer } from './component-state/component-state.reducer'; export interface NiFiState { router: RouterReducerState; @@ -41,6 +43,7 @@ export interface NiFiState { [statusHistoryFeatureKey]: StatusHistoryState; [controllerServiceStateFeatureKey]: ControllerServiceState; [systemDiagnosticsFeatureKey]: SystemDiagnosticsState; + [componentStateFeatureKey]: ComponentStateState; } export const rootReducers: ActionReducerMap = { @@ -51,5 +54,6 @@ export const rootReducers: ActionReducerMap = { [flowConfigurationFeatureKey]: flowConfigurationReducer, [statusHistoryFeatureKey]: statusHistoryReducer, [controllerServiceStateFeatureKey]: controllerServiceStateReducer, - [systemDiagnosticsFeatureKey]: systemDiagnosticsReducer + [systemDiagnosticsFeatureKey]: systemDiagnosticsReducer, + [componentStateFeatureKey]: componentStateReducer }; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts index 7f41618bf3..3f565207e0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts @@ -26,7 +26,6 @@ import { reloadStatusHistorySuccess, getStatusHistoryAndOpenDialog } from './status-history.actions'; -import { produce } from 'immer'; export const initialState: StatusHistoryState = { statusHistory: {} as StatusHistoryEntity, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.html new file mode 100644 index 0000000000..b04dbeea01 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.html @@ -0,0 +1,86 @@ + + +
+

Component State

+ +
+
+
Name
+
{{ componentName }}
+
+
+
Description
+
+ {{ stateDescription }} +
+
+
+
+
Displaying {{ filteredEntries }} of {{ totalEntries }}
+
+ + Filter + + + + + +
+
+
+ + + + + + + + + + + + + + + +
Key + {{ item.key }} + Value + {{ item.value }} +
+
+
+
Showing partial results
+
+
+ + + +
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.scss new file mode 100644 index 0000000000..17606c83f5 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.scss @@ -0,0 +1,32 @@ +/* + * 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; + +.component-state-dialog { + @include mat.button-density(-1); + + width: 760px; + + .listing-table { + table { + .mat-column-key { + width: 200px; + } + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.spec.ts new file mode 100644 index 0000000000..033fe2a105 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.spec.ts @@ -0,0 +1,42 @@ +/* + * 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 { ComponentStateDialog } from './component-state.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { provideMockStore } from '@ngrx/store/testing'; +import { initialState } from '../../../state/component-state/component-state.reducer'; + +describe('ComponentStateDialog', () => { + let component: ComponentStateDialog; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ComponentStateDialog, BrowserAnimationsModule], + providers: [provideMockStore({ initialState })] + }); + fixture = TestBed.createComponent(ComponentStateDialog); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.ts new file mode 100644 index 0000000000..8f37bb96c7 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.ts @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AfterViewInit, Component, Input } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { NiFiCommon } from '../../../service/nifi-common.service'; +import { MatSortModule, Sort } from '@angular/material/sort'; +import { AsyncPipe, NgIf } from '@angular/common'; +import { NifiTooltipDirective } from '../tooltips/nifi-tooltip.directive'; +import { NifiSpinnerDirective } from '../spinner/nifi-spinner.directive'; +import { ComponentStateState, StateEntry, StateMap } from '../../../state/component-state'; +import { Store } from '@ngrx/store'; +import { clearComponentState } from '../../../state/component-state/component-state.actions'; +import { + selectCanClear, + selectComponentName, + selectComponentState +} from '../../../state/component-state/component-state.selectors'; +import { isDefinedAndNotNull } from '../../../state/shared'; +import { debounceTime, Observable } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; + +@Component({ + selector: 'component-state', + standalone: true, + templateUrl: './component-state.component.html', + imports: [ + MatButtonModule, + MatDialogModule, + MatTableModule, + MatSortModule, + NgIf, + NifiTooltipDirective, + NifiSpinnerDirective, + AsyncPipe, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule + ], + styleUrls: ['./component-state.component.scss', '../../../../assets/styles/listing-table.scss'] +}) +export class ComponentStateDialog implements AfterViewInit { + @Input() initialSortColumn: 'key' | 'value' = 'key'; + @Input() initialSortDirection: 'asc' | 'desc' = 'asc'; + + componentName$: Observable = this.store.select(selectComponentName).pipe(isDefinedAndNotNull()); + canClear$: Observable = this.store.select(selectCanClear).pipe(isDefinedAndNotNull()); + + // TODO - need to include scope column when clustered + displayedColumns: string[] = ['key', 'value']; + dataSource: MatTableDataSource = new MatTableDataSource(); + + filterForm: FormGroup; + + stateDescription: string = ''; + totalEntries: number = 0; + filteredEntries: number = 0; + partialResults: boolean = false; + + constructor( + private store: Store, + private formBuilder: FormBuilder, + private nifiCommon: NiFiCommon + ) { + this.filterForm = this.formBuilder.group({ filterTerm: '' }); + + this.store + .select(selectComponentState) + .pipe(isDefinedAndNotNull(), takeUntilDestroyed()) + .subscribe((componentState) => { + this.stateDescription = componentState.stateDescription; + + const stateItems: StateEntry[] = []; + if (componentState.localState) { + const localStateItems: StateEntry[] = this.processStateMap(componentState.localState); + stateItems.push(...localStateItems); + } + if (componentState.clusterState) { + const clusterStateItems: StateEntry[] = this.processStateMap(componentState.clusterState); + stateItems.push(...clusterStateItems); + } + + this.dataSource.data = this.sortStateEntries(stateItems, { + active: this.initialSortColumn, + direction: this.initialSortDirection + }); + this.filteredEntries = stateItems.length; + + // apply any filtering to the new data + const filterTerm = this.filterForm.get('filterTerm')?.value; + if (filterTerm?.length > 0) { + this.applyFilter(filterTerm); + } + }); + } + + ngAfterViewInit(): void { + this.filterForm + .get('filterTerm') + ?.valueChanges.pipe(debounceTime(500)) + .subscribe((filterTerm: string) => { + this.applyFilter(filterTerm); + }); + } + + processStateMap(stateMap: StateMap): StateEntry[] { + const stateItems: StateEntry[] = stateMap.state ? stateMap.state : []; + + if (stateItems.length !== stateMap.totalEntryCount) { + this.partialResults = true; + } + + this.totalEntries += stateMap.totalEntryCount; + + return stateItems; + } + + applyFilter(filterTerm: string) { + this.dataSource.filter = filterTerm.trim().toLowerCase(); + this.filteredEntries = this.dataSource.filteredData.length; + } + + sortData(sort: Sort) { + this.dataSource.data = this.sortStateEntries(this.dataSource.data, sort); + } + + private sortStateEntries(data: StateEntry[], sort: Sort): StateEntry[] { + if (!data) { + return []; + } + + return data.slice().sort((a, b) => { + const isAsc = sort.direction === 'asc'; + let retVal = 0; + switch (sort.active) { + case 'key': + retVal = this.nifiCommon.compareString(a.key, b.key); + break; + case 'value': + retVal = this.nifiCommon.compareString(a.value, b.value); + break; + } + return retVal * (isAsc ? 1 : -1); + }); + } + + clearState(): void { + this.store.dispatch(clearComponentState()); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html index 03076a9a03..0ae491ed2f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html @@ -141,7 +141,11 @@ *ngIf="canDelete(item)" (click)="deleteClicked(item, $event)" title="Delete">
-
+
(); @Output() disableControllerService: EventEmitter = new EventEmitter(); + @Output() viewStateControllerService: EventEmitter = + new EventEmitter(); protected readonly TextTip = TextTip; protected readonly BulletinsTip = BulletinsTip; @@ -251,6 +253,10 @@ export class ControllerServiceTable { return this.canRead(entity) && this.canWrite(entity) && entity.component.persistsState === true; } + viewStateClicked(entity: ControllerServiceEntity): void { + this.viewStateControllerService.next(entity); + } + canManageAccessPolicies(): boolean { return this.flowConfiguration.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.html index ba83e572d8..e850ee7ef7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.html @@ -100,7 +100,6 @@