mirror of https://github.com/apache/nifi.git
[NIFI-13318] processor stop and configure UX (#9548)
* [NIFI-13318] processor stop and configure UX * handle invalid run status * display validation errors * invalid icon and tooltip alternative placement * remove validation errors tooltip, move invalid icon into status button, update edit processor entity and readonly updates * restore MAT_DIALOG_DATA and only enable/disable form controls via api * clean up * only allow updates by current client and poll until stopped and no active threads * update menu options to display when available * display processor bulletins * align dialog header text and run status button * update filter for incoming updated entities and submit appropriate revision on run status changes * disable button when stopping * code clean up * update method name * update error message * update types * add types and cleanup * move run status action button * review feedback * update run status action button to consider when user cannot operate processor * update to also handle disabled run status when user does not have operate * clean up pollingProcessor * disable button when stopping * prettier * prettier * readd thread count * poll when necessary This closes #9548
This commit is contained in:
parent
a5086a9eb2
commit
0271e926eb
|
@ -524,7 +524,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidRevisionException(revision + " is not the most up-to-date revision. This component appears to have been modified");
|
throw new InvalidRevisionException(revision + " is not the most up-to-date revision. This component appears to have been modified. Retrieve the most up-to-date revision and try again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -36,6 +36,7 @@ yarn-error.log
|
||||||
/libpeerconnection.log
|
/libpeerconnection.log
|
||||||
testem.log
|
testem.log
|
||||||
/typings
|
/typings
|
||||||
|
/.tool-versions
|
||||||
|
|
||||||
# System files
|
# System files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
@ -87,6 +87,7 @@ import {
|
||||||
StartComponentRequest,
|
StartComponentRequest,
|
||||||
StartComponentResponse,
|
StartComponentResponse,
|
||||||
StartComponentsRequest,
|
StartComponentsRequest,
|
||||||
|
StartPollingProcessorUntilStoppedRequest,
|
||||||
StartProcessGroupRequest,
|
StartProcessGroupRequest,
|
||||||
StartProcessGroupResponse,
|
StartProcessGroupResponse,
|
||||||
StopComponentRequest,
|
StopComponentRequest,
|
||||||
|
@ -776,6 +777,20 @@ export const pollChangeVersionSuccess = createAction(
|
||||||
|
|
||||||
export const stopPollingChangeVersion = createAction(`${CANVAS_PREFIX} Stop Polling Change Version`);
|
export const stopPollingChangeVersion = createAction(`${CANVAS_PREFIX} Stop Polling Change Version`);
|
||||||
|
|
||||||
|
export const startPollingProcessorUntilStopped = createAction(
|
||||||
|
`${CANVAS_PREFIX} Start Polling Processor Until Stopped`,
|
||||||
|
props<{ request: StartPollingProcessorUntilStoppedRequest }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const pollProcessorUntilStopped = createAction(`${CANVAS_PREFIX} Poll Processor Until Stopped`);
|
||||||
|
|
||||||
|
export const pollProcessorUntilStoppedSuccess = createAction(
|
||||||
|
`${CANVAS_PREFIX} Poll Processor Until Stopped Success`,
|
||||||
|
props<{ response: LoadProcessorSuccess }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const stopPollingProcessor = createAction(`${CANVAS_PREFIX} Stop Polling Processor`);
|
||||||
|
|
||||||
export const openSaveVersionDialog = createAction(
|
export const openSaveVersionDialog = createAction(
|
||||||
`${CANVAS_PREFIX} Open Save Flow Version Dialog`,
|
`${CANVAS_PREFIX} Open Save Flow Version Dialog`,
|
||||||
props<{ request: SaveVersionDialogRequest }>()
|
props<{ request: SaveVersionDialogRequest }>()
|
||||||
|
|
|
@ -45,6 +45,8 @@ import {
|
||||||
CreateConnectionDialogRequest,
|
CreateConnectionDialogRequest,
|
||||||
CreateProcessGroupDialogRequest,
|
CreateProcessGroupDialogRequest,
|
||||||
DeleteComponentResponse,
|
DeleteComponentResponse,
|
||||||
|
DisableComponentRequest,
|
||||||
|
EnableComponentRequest,
|
||||||
GroupComponentsDialogRequest,
|
GroupComponentsDialogRequest,
|
||||||
ImportFromRegistryDialogRequest,
|
ImportFromRegistryDialogRequest,
|
||||||
LoadProcessGroupResponse,
|
LoadProcessGroupResponse,
|
||||||
|
@ -56,6 +58,8 @@ import {
|
||||||
SaveVersionRequest,
|
SaveVersionRequest,
|
||||||
SelectedComponent,
|
SelectedComponent,
|
||||||
Snippet,
|
Snippet,
|
||||||
|
StartComponentRequest,
|
||||||
|
StopComponentRequest,
|
||||||
StopVersionControlRequest,
|
StopVersionControlRequest,
|
||||||
StopVersionControlResponse,
|
StopVersionControlResponse,
|
||||||
UpdateComponentFailure,
|
UpdateComponentFailure,
|
||||||
|
@ -80,6 +84,7 @@ import {
|
||||||
selectParentProcessGroupId,
|
selectParentProcessGroupId,
|
||||||
selectProcessGroup,
|
selectProcessGroup,
|
||||||
selectProcessor,
|
selectProcessor,
|
||||||
|
selectPollingProcessor,
|
||||||
selectRefreshRpgDetails,
|
selectRefreshRpgDetails,
|
||||||
selectRemoteProcessGroup,
|
selectRemoteProcessGroup,
|
||||||
selectSaving,
|
selectSaving,
|
||||||
|
@ -160,6 +165,13 @@ import { selectDocumentVisibilityState } from '../../../../state/document-visibi
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { DocumentVisibility } from '../../../../state/document-visibility';
|
import { DocumentVisibility } from '../../../../state/document-visibility';
|
||||||
import { ErrorContextKey } from '../../../../state/error';
|
import { ErrorContextKey } from '../../../../state/error';
|
||||||
|
import {
|
||||||
|
disableComponent,
|
||||||
|
enableComponent,
|
||||||
|
startComponent,
|
||||||
|
startPollingProcessorUntilStopped,
|
||||||
|
stopComponent
|
||||||
|
} from './flow.actions';
|
||||||
import { CopyPasteService } from '../../service/copy-paste.service';
|
import { CopyPasteService } from '../../service/copy-paste.service';
|
||||||
import { selectCopiedContent } from '../../../../state/copy/copy.selectors';
|
import { selectCopiedContent } from '../../../../state/copy/copy.selectors';
|
||||||
import { CopyRequestContext, CopyResponseContext } from '../../../../state/copy';
|
import { CopyRequestContext, CopyResponseContext } from '../../../../state/copy';
|
||||||
|
@ -1428,6 +1440,7 @@ export class FlowEffects {
|
||||||
}),
|
}),
|
||||||
tap(([request, parameterContext, processGroupId]) => {
|
tap(([request, parameterContext, processGroupId]) => {
|
||||||
const processorId: string = request.entity.id;
|
const processorId: string = request.entity.id;
|
||||||
|
let runStatusChanged: boolean = false;
|
||||||
|
|
||||||
const editDialogReference = this.dialog.open(EditProcessor, {
|
const editDialogReference = this.dialog.open(EditProcessor, {
|
||||||
...XL_DIALOG,
|
...XL_DIALOG,
|
||||||
|
@ -1555,6 +1568,116 @@ export class FlowEffects {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
const startPollingIfNecessary = (processorEntity: any): boolean => {
|
||||||
|
if (
|
||||||
|
(processorEntity.status.aggregateSnapshot.runStatus === 'Stopped' &&
|
||||||
|
processorEntity.status.aggregateSnapshot.activeThreadCount > 0) ||
|
||||||
|
processorEntity.status.aggregateSnapshot.runStatus === 'Validating'
|
||||||
|
) {
|
||||||
|
this.store.dispatch(
|
||||||
|
startPollingProcessorUntilStopped({
|
||||||
|
request: {
|
||||||
|
id: processorEntity.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pollingStarted = startPollingIfNecessary(request.entity);
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.select(selectProcessor(processorId))
|
||||||
|
.pipe(
|
||||||
|
takeUntil(editDialogReference.afterClosed()),
|
||||||
|
isDefinedAndNotNull(),
|
||||||
|
filter((processorEntity) => {
|
||||||
|
return (
|
||||||
|
(runStatusChanged || pollingStarted) &&
|
||||||
|
processorEntity.revision.clientId === this.client.getClientId()
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
concatLatestFrom(() => this.store.select(selectPollingProcessor))
|
||||||
|
)
|
||||||
|
.subscribe(([processorEntity, pollingProcessor]) => {
|
||||||
|
editDialogReference.componentInstance.processorUpdates = processorEntity;
|
||||||
|
|
||||||
|
// if we're already polling we do not want to start polling again
|
||||||
|
if (!pollingProcessor) {
|
||||||
|
startPollingIfNecessary(processorEntity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
editDialogReference.componentInstance.stopComponentRequest
|
||||||
|
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||||
|
.subscribe((stopComponentRequest: StopComponentRequest) => {
|
||||||
|
runStatusChanged = true;
|
||||||
|
this.store.dispatch(
|
||||||
|
stopComponent({
|
||||||
|
request: {
|
||||||
|
id: stopComponentRequest.id,
|
||||||
|
uri: stopComponentRequest.uri,
|
||||||
|
type: ComponentType.Processor,
|
||||||
|
revision: stopComponentRequest.revision,
|
||||||
|
errorStrategy: 'snackbar'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
editDialogReference.componentInstance.disableComponentRequest
|
||||||
|
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||||
|
.subscribe((disableComponentsRequest: DisableComponentRequest) => {
|
||||||
|
runStatusChanged = true;
|
||||||
|
this.store.dispatch(
|
||||||
|
disableComponent({
|
||||||
|
request: {
|
||||||
|
id: disableComponentsRequest.id,
|
||||||
|
uri: disableComponentsRequest.uri,
|
||||||
|
type: ComponentType.Processor,
|
||||||
|
revision: disableComponentsRequest.revision,
|
||||||
|
errorStrategy: 'snackbar'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
editDialogReference.componentInstance.enableComponentRequest
|
||||||
|
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||||
|
.subscribe((enableComponentsRequest: EnableComponentRequest) => {
|
||||||
|
runStatusChanged = true;
|
||||||
|
this.store.dispatch(
|
||||||
|
enableComponent({
|
||||||
|
request: {
|
||||||
|
id: enableComponentsRequest.id,
|
||||||
|
uri: enableComponentsRequest.uri,
|
||||||
|
type: ComponentType.Processor,
|
||||||
|
revision: enableComponentsRequest.revision,
|
||||||
|
errorStrategy: 'snackbar'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
editDialogReference.componentInstance.startComponentRequest
|
||||||
|
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||||
|
.subscribe((startComponentRequest: StartComponentRequest) => {
|
||||||
|
runStatusChanged = true;
|
||||||
|
this.store.dispatch(
|
||||||
|
startComponent({
|
||||||
|
request: {
|
||||||
|
id: startComponentRequest.id,
|
||||||
|
uri: startComponentRequest.uri,
|
||||||
|
type: ComponentType.Processor,
|
||||||
|
revision: startComponentRequest.revision,
|
||||||
|
errorStrategy: 'snackbar'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
editDialogReference.afterClosed().subscribe((response) => {
|
editDialogReference.afterClosed().subscribe((response) => {
|
||||||
this.store.dispatch(resetPropertyVerificationState());
|
this.store.dispatch(resetPropertyVerificationState());
|
||||||
|
@ -1578,6 +1701,57 @@ export class FlowEffects {
|
||||||
{ dispatch: false }
|
{ dispatch: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
startPollingProcessorUntilStopped = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(FlowActions.startPollingProcessorUntilStopped),
|
||||||
|
switchMap(() =>
|
||||||
|
interval(2000, asyncScheduler).pipe(
|
||||||
|
takeUntil(this.actions$.pipe(ofType(FlowActions.stopPollingProcessor)))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
switchMap(() => of(FlowActions.pollProcessorUntilStopped()))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
pollProcessorUntilStopped$ = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(FlowActions.pollProcessorUntilStopped),
|
||||||
|
concatLatestFrom(() => [this.store.select(selectPollingProcessor).pipe(isDefinedAndNotNull())]),
|
||||||
|
switchMap(([, pollingProcessor]) => {
|
||||||
|
return from(
|
||||||
|
this.flowService.getProcessor(pollingProcessor.id).pipe(
|
||||||
|
map((response) =>
|
||||||
|
FlowActions.pollProcessorUntilStoppedSuccess({
|
||||||
|
response: {
|
||||||
|
id: pollingProcessor.id,
|
||||||
|
processor: response
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
catchError((errorResponse: HttpErrorResponse) => {
|
||||||
|
this.store.dispatch(FlowActions.stopPollingProcessor());
|
||||||
|
return of(this.snackBarOrFullScreenError(errorResponse));
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
pollProcessorUntilStoppedSuccess$ = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(FlowActions.pollProcessorUntilStoppedSuccess),
|
||||||
|
map((action) => action.response),
|
||||||
|
filter((response) => {
|
||||||
|
return (
|
||||||
|
response.processor.status.runStatus === 'Stopped' &&
|
||||||
|
response.processor.status.aggregateSnapshot.activeThreadCount === 0
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
switchMap(() => of(FlowActions.stopPollingProcessor()))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
openEditConnectionDialog$ = createEffect(
|
openEditConnectionDialog$ = createEffect(
|
||||||
() =>
|
() =>
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
|
|
|
@ -50,6 +50,7 @@ import {
|
||||||
navigateWithoutTransform,
|
navigateWithoutTransform,
|
||||||
pasteSuccess,
|
pasteSuccess,
|
||||||
pollChangeVersionSuccess,
|
pollChangeVersionSuccess,
|
||||||
|
pollProcessorUntilStoppedSuccess,
|
||||||
pollRevertChangesSuccess,
|
pollRevertChangesSuccess,
|
||||||
requestRefreshRemoteProcessGroup,
|
requestRefreshRemoteProcessGroup,
|
||||||
resetFlowState,
|
resetFlowState,
|
||||||
|
@ -68,10 +69,12 @@ import {
|
||||||
setTransitionRequired,
|
setTransitionRequired,
|
||||||
startComponent,
|
startComponent,
|
||||||
startComponentSuccess,
|
startComponentSuccess,
|
||||||
|
startPollingProcessorUntilStopped,
|
||||||
startProcessGroupSuccess,
|
startProcessGroupSuccess,
|
||||||
startRemoteProcessGroupPolling,
|
startRemoteProcessGroupPolling,
|
||||||
stopComponent,
|
stopComponent,
|
||||||
stopComponentSuccess,
|
stopComponentSuccess,
|
||||||
|
stopPollingProcessor,
|
||||||
stopProcessGroupSuccess,
|
stopProcessGroupSuccess,
|
||||||
stopRemoteProcessGroupPolling,
|
stopRemoteProcessGroupPolling,
|
||||||
stopVersionControl,
|
stopVersionControl,
|
||||||
|
@ -92,6 +95,7 @@ import { produce } from 'immer';
|
||||||
export const initialState: FlowState = {
|
export const initialState: FlowState = {
|
||||||
id: 'root',
|
id: 'root',
|
||||||
changeVersionRequest: null,
|
changeVersionRequest: null,
|
||||||
|
pollingProcessor: null,
|
||||||
flow: {
|
flow: {
|
||||||
revision: {
|
revision: {
|
||||||
version: 0
|
version: 0
|
||||||
|
@ -297,7 +301,7 @@ export const flowReducer = createReducer(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
on(loadProcessorSuccess, (state, { response }) => {
|
on(loadProcessorSuccess, pollProcessorUntilStoppedSuccess, (state, { response }) => {
|
||||||
return produce(state, (draftState) => {
|
return produce(state, (draftState) => {
|
||||||
const proposedProcessor = response.processor;
|
const proposedProcessor = response.processor;
|
||||||
const componentIndex: number = draftState.flow.processGroupFlow.flow.processors.findIndex(
|
const componentIndex: number = draftState.flow.processGroupFlow.flow.processors.findIndex(
|
||||||
|
@ -373,6 +377,14 @@ export const flowReducer = createReducer(
|
||||||
saving: false,
|
saving: false,
|
||||||
versionSaving: false
|
versionSaving: false
|
||||||
})),
|
})),
|
||||||
|
on(startPollingProcessorUntilStopped, (state, { request }) => ({
|
||||||
|
...state,
|
||||||
|
pollingProcessor: request
|
||||||
|
})),
|
||||||
|
on(stopPollingProcessor, (state) => ({
|
||||||
|
...state,
|
||||||
|
pollingProcessor: null
|
||||||
|
})),
|
||||||
on(
|
on(
|
||||||
createProcessor,
|
createProcessor,
|
||||||
createProcessGroup,
|
createProcessGroup,
|
||||||
|
|
|
@ -30,6 +30,8 @@ export const selectChangeVersionRequest = createSelector(
|
||||||
(state: FlowState) => state.changeVersionRequest
|
(state: FlowState) => state.changeVersionRequest
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectPollingProcessor = createSelector(selectFlowState, (state: FlowState) => state.pollingProcessor);
|
||||||
|
|
||||||
export const selectSaving = createSelector(selectFlowState, (state: FlowState) => state.saving);
|
export const selectSaving = createSelector(selectFlowState, (state: FlowState) => state.saving);
|
||||||
|
|
||||||
export const selectVersionSaving = createSelector(selectFlowState, (state: FlowState) => state.versionSaving);
|
export const selectVersionSaving = createSelector(selectFlowState, (state: FlowState) => state.versionSaving);
|
||||||
|
|
|
@ -659,6 +659,7 @@ export interface FlowState {
|
||||||
flowAnalysisOpen: boolean;
|
flowAnalysisOpen: boolean;
|
||||||
versionSaving: boolean;
|
versionSaving: boolean;
|
||||||
changeVersionRequest: FlowUpdateRequestEntity | null;
|
changeVersionRequest: FlowUpdateRequestEntity | null;
|
||||||
|
pollingProcessor: StartPollingProcessorUntilStoppedRequest | null;
|
||||||
status: 'pending' | 'loading' | 'success' | 'complete';
|
status: 'pending' | 'loading' | 'success' | 'complete';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,6 +793,10 @@ export interface StopComponentRequest {
|
||||||
errorStrategy: 'snackbar' | 'banner';
|
errorStrategy: 'snackbar' | 'banner';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StartPollingProcessorUntilStoppedRequest {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StopProcessGroupRequest {
|
export interface StopProcessGroupRequest {
|
||||||
id: string;
|
id: string;
|
||||||
type: ComponentType;
|
type: ComponentType;
|
||||||
|
|
|
@ -40,11 +40,6 @@
|
||||||
neutral,
|
neutral,
|
||||||
map.get(map.get($config, neutral), lighter)
|
map.get(map.get($config, neutral), lighter)
|
||||||
);
|
);
|
||||||
$material-theme-neutral-palette-default: mat.get-theme-color(
|
|
||||||
$material-theme,
|
|
||||||
neutral,
|
|
||||||
map.get(map.get($config, neutral), default)
|
|
||||||
);
|
|
||||||
|
|
||||||
$material-theme-primary-palette-default: mat.get-theme-color(
|
$material-theme-primary-palette-default: mat.get-theme-color(
|
||||||
$material-theme,
|
$material-theme,
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* 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 'sass:map';
|
||||||
|
@use '@angular/material' as mat;
|
||||||
|
|
||||||
|
@mixin generate-theme($material-theme, $config) {
|
||||||
|
$is-material-dark: if(mat.get-theme-type($material-theme) == dark, true, false);
|
||||||
|
$material-theme-secondary-palette-default: mat.get-theme-color(
|
||||||
|
$material-theme,
|
||||||
|
secondary,
|
||||||
|
map.get(map.get($config, secondary), default)
|
||||||
|
);
|
||||||
|
$material-theme-error-palette-default: mat.get-theme-color(
|
||||||
|
$material-theme,
|
||||||
|
error,
|
||||||
|
map.get(map.get($config, error), default)
|
||||||
|
);
|
||||||
|
|
||||||
|
$material-theme-primary-palette-default: mat.get-theme-color(
|
||||||
|
$material-theme,
|
||||||
|
primary,
|
||||||
|
map.get(map.get($config, primary), default)
|
||||||
|
);
|
||||||
|
|
||||||
|
$primary-contrast: map.get(map.get($config, primary), contrast);
|
||||||
|
$caution-contrast: map.get(map.get($config, caution), contrast);
|
||||||
|
$error-contrast: map.get(map.get($config, error), contrast);
|
||||||
|
$success: map.get(map.get($config, success), default);
|
||||||
|
$caution: map.get(map.get($config, caution), default);
|
||||||
|
|
||||||
|
#edit-processor-header {
|
||||||
|
.bulletins {
|
||||||
|
background-color: unset;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
color: $material-theme-primary-palette-default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulletins.has-bulletins {
|
||||||
|
.fa {
|
||||||
|
color: $primary-contrast;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
.fa {
|
||||||
|
color: $error-contrast;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: $material-theme-error-palette-default;
|
||||||
|
}
|
||||||
|
&.warning {
|
||||||
|
.fa {
|
||||||
|
color: $caution-contrast;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: $caution;
|
||||||
|
}
|
||||||
|
&.info,
|
||||||
|
&.debug,
|
||||||
|
&.trace {
|
||||||
|
background-color: $success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,19 +15,37 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<h2 mat-dialog-title>
|
<h2 id="edit-processor-header" mat-dialog-title>
|
||||||
<div class="flex justify-between items-baseline">
|
<div class="flex justify-between items-center">
|
||||||
<div>
|
<div class="flex items-baseline">
|
||||||
{{ readonly ? 'Processor Details' : 'Edit Processor' }}
|
<div class="mr-2">
|
||||||
|
{{ readonly ? 'Processor Details' : 'Edit Processor' }}
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
<div class="ml-2 text-base">
|
||||||
|
{{ formatType() }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-base">
|
<div class="flex">
|
||||||
{{ formatType(request.entity) }}
|
@if (hasBulletins()) {
|
||||||
|
<div class="w-[48px]">
|
||||||
|
<div
|
||||||
|
nifiTooltip
|
||||||
|
[delayClose]="true"
|
||||||
|
[tooltipComponentType]="BulletinsTip"
|
||||||
|
[tooltipInputData]="getBulletinsTipData()"
|
||||||
|
[position]="getBulletinTooltipPosition()"
|
||||||
|
[ngClass]="getMostSevereBulletinLevel()"
|
||||||
|
class="absolute top-0 right-0 text-3xl h-14 w-14 bulletins has-bulletins flex justify-center items-center">
|
||||||
|
<i class="fa fa-sticky-note-o"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
<form class="processor-edit-form" [formGroup]="editProcessorForm">
|
<form class="processor-edit-form" [formGroup]="editProcessorForm">
|
||||||
<context-error-banner [context]="ErrorContextKey.PROCESSOR"></context-error-banner>
|
<context-error-banner [context]="ErrorContextKey.PROCESSOR"></context-error-banner>
|
||||||
<!-- TODO - Stop & Configure -->
|
|
||||||
<mat-tab-group [(selectedIndex)]="selectedIndex" (selectedIndexChange)="tabChanged($event)">
|
<mat-tab-group [(selectedIndex)]="selectedIndex" (selectedIndexChange)="tabChanged($event)">
|
||||||
<mat-tab label="Settings">
|
<mat-tab label="Settings">
|
||||||
<mat-dialog-content>
|
<mat-dialog-content>
|
||||||
|
@ -48,13 +66,13 @@
|
||||||
<div class="flex flex-col mb-5">
|
<div class="flex flex-col mb-5">
|
||||||
<div>Type</div>
|
<div>Type</div>
|
||||||
<div class="tertiary-color font-medium">
|
<div class="tertiary-color font-medium">
|
||||||
{{ formatType(request.entity) }}
|
{{ formatType() }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col mb-6">
|
<div class="flex flex-col mb-6">
|
||||||
<div>Bundle</div>
|
<div>Bundle</div>
|
||||||
<div class="tertiary-color font-medium">
|
<div class="tertiary-color font-medium">
|
||||||
{{ formatBundle(request.entity) }}
|
{{ formatBundle() }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-x-4">
|
<div class="flex gap-x-4">
|
||||||
|
@ -290,19 +308,106 @@
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
@if ({ value: (saving$ | async)! }; as saving) {
|
@if ({ value: (saving$ | async)! }; as saving) {
|
||||||
<mat-dialog-actions align="end">
|
<mat-dialog-actions align="start">
|
||||||
@if (readonly) {
|
<div class="flex w-full justify-between items-center">
|
||||||
<button mat-flat-button mat-dialog-close>Close</button>
|
<div>
|
||||||
} @else {
|
@if (isStoppable()) {
|
||||||
<button mat-button mat-dialog-close>Cancel</button>
|
<button
|
||||||
<button
|
type="button"
|
||||||
[disabled]="!editProcessorForm.dirty || editProcessorForm.invalid || saving.value"
|
[disabled]="!canOperate()"
|
||||||
type="button"
|
mat-stroked-button
|
||||||
(click)="submitForm()"
|
[matMenuTriggerFor]="operateMenu">
|
||||||
mat-flat-button>
|
<div class="flex items-center">
|
||||||
<span *nifiSpinner="saving.value">Apply</span>
|
<i class="mr-2 success-color-default fa fa-play"></i>Running<i
|
||||||
</button>
|
class="ml-2 -mt-1 fa fa-sort-desc"></i>
|
||||||
}
|
</div>
|
||||||
|
</button>
|
||||||
|
} @else if (isRunnable()) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
[disabled]="!canOperate()"
|
||||||
|
mat-stroked-button
|
||||||
|
[matMenuTriggerFor]="operateMenu">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i class="mr-2 error-color-variant fa fa-stop"></i>Stopped<i
|
||||||
|
class="ml-2 -mt-1 fa fa-sort-desc"></i>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
} @else if (isDisabled()) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
[disabled]="!canOperate()"
|
||||||
|
mat-stroked-button
|
||||||
|
[matMenuTriggerFor]="operateMenu">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i class="mr-2 icon icon-enable-false primary-color"></i>Disable<i
|
||||||
|
class="ml-2 -mt-1 fa fa-sort-desc"></i>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
} @else if (isStopping()) {
|
||||||
|
<button type="button" [disabled]="true" mat-stroked-button [matMenuTriggerFor]="operateMenu">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i class="mr-2 fa fa-circle-o-notch fa-spin primary-color"></i>Stopping ({{
|
||||||
|
status.aggregateSnapshot.activeThreadCount
|
||||||
|
}})
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
} @else if (isValidating()) {
|
||||||
|
<button type="button" [disabled]="true" mat-stroked-button [matMenuTriggerFor]="operateMenu">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i class="mr-2 fa fa-circle-o-notch fa-spin primary-color"></i>Validating
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
} @else if (isInvalid()) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
mat-stroked-button
|
||||||
|
[disabled]="!canOperate()"
|
||||||
|
[matMenuTriggerFor]="operateMenu">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i class="mr-2 fa fa-warning caution-color"></i>Invalid<i
|
||||||
|
class="ml-2 -mt-1 fa fa-sort-desc"></i>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
<mat-menu #operateMenu="matMenu" xPosition="before">
|
||||||
|
@if (isStoppable() && canOperate()) {
|
||||||
|
<button mat-menu-item (click)="stop()">
|
||||||
|
<i class="mr-2 fa fa-stop primary-color"></i>Stop
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
@if (isRunnable() && canOperate()) {
|
||||||
|
<button mat-menu-item [disabled]="editProcessorForm.dirty" (click)="start()">
|
||||||
|
<i class="mr-2 fa fa-play primary-color"></i>Start
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
@if (isDisableable() && canOperate()) {
|
||||||
|
<button mat-menu-item [disabled]="editProcessorForm.dirty" (click)="disable()">
|
||||||
|
<i class="mr-2 icon icon-enable-false primary-color"></i>Disable
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
@if (isEnableable() && canOperate()) {
|
||||||
|
<button mat-menu-item [disabled]="editProcessorForm.dirty" (click)="enable()">
|
||||||
|
<i class="mr-2 fa fa-flash primary-color"></i>Enable
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</mat-menu>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (readonly) {
|
||||||
|
<button mat-flat-button mat-dialog-close>Close</button>
|
||||||
|
} @else {
|
||||||
|
<button mat-button mat-dialog-close>Cancel</button>
|
||||||
|
<button
|
||||||
|
[disabled]="!editProcessorForm.dirty || editProcessorForm.invalid || saving.value"
|
||||||
|
type="button"
|
||||||
|
(click)="submitForm()"
|
||||||
|
mat-flat-button>
|
||||||
|
<span *nifiSpinner="saving.value">Apply</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</mat-dialog-actions>
|
</mat-dialog-actions>
|
||||||
}
|
}
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
import { Component, EventEmitter, Inject, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, Inject, Input, Output } from '@angular/core';
|
||||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import {
|
import {
|
||||||
AbstractControl,
|
AbstractControl,
|
||||||
FormBuilder,
|
FormBuilder,
|
||||||
|
@ -30,19 +31,29 @@ import {
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { AsyncPipe } from '@angular/common';
|
import { AsyncPipe, NgClass } from '@angular/common';
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
import { MatOptionModule } from '@angular/material/core';
|
import { MatOptionModule } from '@angular/material/core';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
|
BulletinEntity,
|
||||||
|
BulletinsTipInput,
|
||||||
InlineServiceCreationRequest,
|
InlineServiceCreationRequest,
|
||||||
InlineServiceCreationResponse,
|
InlineServiceCreationResponse,
|
||||||
ParameterContextEntity,
|
ParameterContextEntity,
|
||||||
Property
|
Property,
|
||||||
|
Revision
|
||||||
} from '../../../../../../../state/shared';
|
} from '../../../../../../../state/shared';
|
||||||
import { Client } from '../../../../../../../service/client.service';
|
import { Client } from '../../../../../../../service/client.service';
|
||||||
import { EditComponentDialogRequest, UpdateProcessorRequest } from '../../../../../state/flow';
|
import {
|
||||||
|
DisableComponentRequest,
|
||||||
|
EditComponentDialogRequest,
|
||||||
|
EnableComponentRequest,
|
||||||
|
StartComponentRequest,
|
||||||
|
StopComponentRequest,
|
||||||
|
UpdateProcessorRequest
|
||||||
|
} from '../../../../../state/flow';
|
||||||
import { PropertyTable } from '../../../../../../../ui/common/property-table/property-table.component';
|
import { PropertyTable } from '../../../../../../../ui/common/property-table/property-table.component';
|
||||||
import { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nifi-spinner.directive';
|
import { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nifi-spinner.directive';
|
||||||
import { NifiTooltipDirective, NiFiCommon, TextTip, CopyDirective } from '@nifi/shared';
|
import { NifiTooltipDirective, NiFiCommon, TextTip, CopyDirective } from '@nifi/shared';
|
||||||
|
@ -51,7 +62,6 @@ import {
|
||||||
RelationshipConfiguration,
|
RelationshipConfiguration,
|
||||||
RelationshipSettings
|
RelationshipSettings
|
||||||
} from './relationship-settings/relationship-settings.component';
|
} from './relationship-settings/relationship-settings.component';
|
||||||
import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-banner.component';
|
|
||||||
import { ClusterConnectionService } from '../../../../../../../service/cluster-connection.service';
|
import { ClusterConnectionService } from '../../../../../../../service/cluster-connection.service';
|
||||||
import { CanvasUtils } from '../../../../../service/canvas-utils.service';
|
import { CanvasUtils } from '../../../../../service/canvas-utils.service';
|
||||||
import { ConvertToParameterResponse } from '../../../../../service/parameter-helper.service';
|
import { ConvertToParameterResponse } from '../../../../../service/parameter-helper.service';
|
||||||
|
@ -65,6 +75,8 @@ import { TabbedDialog } from '../../../../../../../ui/common/tabbed-dialog/tabbe
|
||||||
import { ComponentType, SelectOption } from 'libs/shared/src';
|
import { ComponentType, SelectOption } from 'libs/shared/src';
|
||||||
import { ErrorContextKey } from '../../../../../../../state/error';
|
import { ErrorContextKey } from '../../../../../../../state/error';
|
||||||
import { ContextErrorBanner } from '../../../../../../../ui/common/context-error-banner/context-error-banner.component';
|
import { ContextErrorBanner } from '../../../../../../../ui/common/context-error-banner/context-error-banner.component';
|
||||||
|
import { BulletinsTip } from '../../../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
|
||||||
|
import { ConnectedPosition } from '@angular/cdk/overlay';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'edit-processor',
|
selector: 'edit-processor',
|
||||||
|
@ -79,20 +91,24 @@ import { ContextErrorBanner } from '../../../../../../../ui/common/context-error
|
||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
MatOptionModule,
|
MatOptionModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
|
MatMenuModule,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
PropertyTable,
|
PropertyTable,
|
||||||
NifiSpinnerDirective,
|
NifiSpinnerDirective,
|
||||||
NifiTooltipDirective,
|
NifiTooltipDirective,
|
||||||
RunDurationSlider,
|
RunDurationSlider,
|
||||||
RelationshipSettings,
|
RelationshipSettings,
|
||||||
ErrorBanner,
|
|
||||||
PropertyVerification,
|
PropertyVerification,
|
||||||
ContextErrorBanner,
|
ContextErrorBanner,
|
||||||
CopyDirective
|
CopyDirective,
|
||||||
|
NgClass
|
||||||
],
|
],
|
||||||
styleUrls: ['./edit-processor.component.scss']
|
styleUrls: ['./edit-processor.component.scss']
|
||||||
})
|
})
|
||||||
export class EditProcessor extends TabbedDialog {
|
export class EditProcessor extends TabbedDialog {
|
||||||
|
@Input() set processorUpdates(processorUpdates: any | undefined) {
|
||||||
|
this.processRunStateUpdates(processorUpdates);
|
||||||
|
}
|
||||||
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
|
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
|
||||||
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
|
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
|
||||||
@Input() parameterContext: ParameterContextEntity | undefined;
|
@Input() parameterContext: ParameterContextEntity | undefined;
|
||||||
|
@ -110,11 +126,20 @@ export class EditProcessor extends TabbedDialog {
|
||||||
|
|
||||||
@Output() verify: EventEmitter<VerifyPropertiesRequestContext> = new EventEmitter<VerifyPropertiesRequestContext>();
|
@Output() verify: EventEmitter<VerifyPropertiesRequestContext> = new EventEmitter<VerifyPropertiesRequestContext>();
|
||||||
@Output() editProcessor: EventEmitter<UpdateProcessorRequest> = new EventEmitter<UpdateProcessorRequest>();
|
@Output() editProcessor: EventEmitter<UpdateProcessorRequest> = new EventEmitter<UpdateProcessorRequest>();
|
||||||
|
@Output() stopComponentRequest: EventEmitter<StopComponentRequest> = new EventEmitter<StopComponentRequest>();
|
||||||
|
@Output() startComponentRequest: EventEmitter<StartComponentRequest> = new EventEmitter<StartComponentRequest>();
|
||||||
|
@Output() disableComponentRequest: EventEmitter<DisableComponentRequest> =
|
||||||
|
new EventEmitter<DisableComponentRequest>();
|
||||||
|
@Output() enableComponentRequest: EventEmitter<EnableComponentRequest> = new EventEmitter<EnableComponentRequest>();
|
||||||
|
|
||||||
protected readonly TextTip = TextTip;
|
protected readonly TextTip = TextTip;
|
||||||
|
protected readonly BulletinsTip = BulletinsTip;
|
||||||
|
|
||||||
editProcessorForm: FormGroup;
|
editProcessorForm: FormGroup;
|
||||||
readonly: boolean;
|
readonly: boolean = true;
|
||||||
|
status: any;
|
||||||
|
revision!: Revision;
|
||||||
|
bulletins!: BulletinEntity[];
|
||||||
|
|
||||||
bulletinLevels = [
|
bulletinLevels = [
|
||||||
{
|
{
|
||||||
|
@ -182,9 +207,6 @@ export class EditProcessor extends TabbedDialog {
|
||||||
) {
|
) {
|
||||||
super('edit-processor-selected-index');
|
super('edit-processor-selected-index');
|
||||||
|
|
||||||
this.readonly =
|
|
||||||
!request.entity.permissions.canWrite || !this.canvasUtils.runnableSupportsModification(request.entity);
|
|
||||||
|
|
||||||
const processorProperties: any = request.entity.component.config.properties;
|
const processorProperties: any = request.entity.component.config.properties;
|
||||||
const properties: Property[] = Object.entries(processorProperties).map((entry: any) => {
|
const properties: Property[] = Object.entries(processorProperties).map((entry: any) => {
|
||||||
const [property, value] = entry;
|
const [property, value] = entry;
|
||||||
|
@ -253,6 +275,32 @@ export class EditProcessor extends TabbedDialog {
|
||||||
new FormControl({ value: this.runDurationMillis, disabled: this.readonly }, Validators.required)
|
new FormControl({ value: this.runDurationMillis, disabled: this.readonly }, Validators.required)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.processRunStateUpdates(request.entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private processRunStateUpdates(entity: any) {
|
||||||
|
this.status = entity.status;
|
||||||
|
this.revision = entity.revision;
|
||||||
|
this.bulletins = entity.bulletins;
|
||||||
|
|
||||||
|
this.readonly = !entity.permissions.canWrite || !this.canvasUtils.runnableSupportsModification(entity);
|
||||||
|
|
||||||
|
if (this.readonly) {
|
||||||
|
this.editProcessorForm.get('properties')?.disable();
|
||||||
|
this.editProcessorForm.get('relationshipConfiguration')?.disable();
|
||||||
|
|
||||||
|
if (this.supportsBatching()) {
|
||||||
|
this.editProcessorForm.get('runDuration')?.disable();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.editProcessorForm.get('properties')?.enable();
|
||||||
|
this.editProcessorForm.get('relationshipConfiguration')?.enable();
|
||||||
|
|
||||||
|
if (this.supportsBatching()) {
|
||||||
|
this.editProcessorForm.get('runDuration')?.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private relationshipConfigurationValidator(): ValidatorFn {
|
private relationshipConfigurationValidator(): ValidatorFn {
|
||||||
|
@ -288,12 +336,12 @@ export class EditProcessor extends TabbedDialog {
|
||||||
return this.request.entity.component.supportsBatching == true;
|
return this.request.entity.component.supportsBatching == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatType(entity: any): string {
|
formatType(): string {
|
||||||
return this.nifiCommon.formatType(entity.component);
|
return this.nifiCommon.formatType(this.request.entity.component);
|
||||||
}
|
}
|
||||||
|
|
||||||
formatBundle(entity: any): string {
|
formatBundle(): string {
|
||||||
return this.nifiCommon.formatBundle(entity.component.bundle);
|
return this.nifiCommon.formatBundle(this.request.entity.component.bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
concurrentTasksChanged(): void {
|
concurrentTasksChanged(): void {
|
||||||
|
@ -351,7 +399,10 @@ export class EditProcessor extends TabbedDialog {
|
||||||
.map((relationship) => relationship.name);
|
.map((relationship) => relationship.name);
|
||||||
|
|
||||||
const payload: any = {
|
const payload: any = {
|
||||||
revision: this.client.getRevision(this.request.entity),
|
revision: this.client.getRevision({
|
||||||
|
...this.request.entity,
|
||||||
|
revision: this.revision
|
||||||
|
}),
|
||||||
disconnectedNodeAcknowledged: this.clusterConnectionService.isDisconnectionAcknowledged(),
|
disconnectedNodeAcknowledged: this.clusterConnectionService.isDisconnectionAcknowledged(),
|
||||||
component: {
|
component: {
|
||||||
id: this.request.entity.id,
|
id: this.request.entity.id,
|
||||||
|
@ -401,6 +452,140 @@ export class EditProcessor extends TabbedDialog {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasBulletins(): boolean {
|
||||||
|
return this.request.entity.permissions.canRead && !this.nifiCommon.isEmpty(this.bulletins);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBulletinsTipData(): BulletinsTipInput {
|
||||||
|
return {
|
||||||
|
bulletins: this.bulletins
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getBulletinTooltipPosition(): ConnectedPosition {
|
||||||
|
return {
|
||||||
|
originX: 'end',
|
||||||
|
originY: 'bottom',
|
||||||
|
overlayX: 'end',
|
||||||
|
overlayY: 'top',
|
||||||
|
offsetX: -8,
|
||||||
|
offsetY: 8
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getMostSevereBulletinLevel(): string | null {
|
||||||
|
// determine the most severe of the bulletins
|
||||||
|
const mostSevere = this.canvasUtils.getMostSevereBulletin(this.bulletins);
|
||||||
|
return mostSevere ? mostSevere.bulletin.level.toLowerCase() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isStoppable(): boolean {
|
||||||
|
return this.status.aggregateSnapshot.runStatus === 'Running';
|
||||||
|
}
|
||||||
|
|
||||||
|
isStopping(): boolean {
|
||||||
|
return (
|
||||||
|
this.status.aggregateSnapshot.runStatus === 'Stopped' && this.status.aggregateSnapshot.activeThreadCount > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidating(): boolean {
|
||||||
|
return this.status.aggregateSnapshot.runStatus === 'Validating';
|
||||||
|
}
|
||||||
|
|
||||||
|
isInvalid(): boolean {
|
||||||
|
return this.status.aggregateSnapshot.runStatus === 'Invalid';
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisabled(): boolean {
|
||||||
|
return this.status.aggregateSnapshot.runStatus === 'Disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunnable(): boolean {
|
||||||
|
return (
|
||||||
|
!(
|
||||||
|
this.status.aggregateSnapshot.runStatus === 'Running' ||
|
||||||
|
this.status.aggregateSnapshot.activeThreadCount > 0
|
||||||
|
) && this.status.aggregateSnapshot.runStatus === 'Stopped'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisableable(): boolean {
|
||||||
|
return (
|
||||||
|
!(
|
||||||
|
this.status.aggregateSnapshot.runStatus === 'Running' ||
|
||||||
|
this.status.aggregateSnapshot.activeThreadCount > 0
|
||||||
|
) &&
|
||||||
|
(this.status.aggregateSnapshot.runStatus === 'Stopped' ||
|
||||||
|
this.status.aggregateSnapshot.runStatus === 'Invalid')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isEnableable(): boolean {
|
||||||
|
return (
|
||||||
|
!(
|
||||||
|
this.status.aggregateSnapshot.runStatus === 'Running' ||
|
||||||
|
this.status.aggregateSnapshot.activeThreadCount > 0
|
||||||
|
) && this.status.aggregateSnapshot.runStatus === 'Disabled'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
canOperate(): boolean {
|
||||||
|
return this.request.entity.permissions.canWrite || this.request.entity.operatePermissions?.canWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.stopComponentRequest.next({
|
||||||
|
id: this.request.entity.id,
|
||||||
|
uri: this.request.entity.uri,
|
||||||
|
type: ComponentType.Processor,
|
||||||
|
revision: this.client.getRevision({
|
||||||
|
...this.request.entity,
|
||||||
|
revision: this.revision
|
||||||
|
}),
|
||||||
|
errorStrategy: 'snackbar'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.startComponentRequest.next({
|
||||||
|
id: this.request.entity.id,
|
||||||
|
uri: this.request.entity.uri,
|
||||||
|
type: ComponentType.Processor,
|
||||||
|
revision: this.client.getRevision({
|
||||||
|
...this.request.entity,
|
||||||
|
revision: this.revision
|
||||||
|
}),
|
||||||
|
errorStrategy: 'snackbar'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
this.disableComponentRequest.next({
|
||||||
|
id: this.request.entity.id,
|
||||||
|
uri: this.request.entity.uri,
|
||||||
|
type: ComponentType.Processor,
|
||||||
|
revision: this.client.getRevision({
|
||||||
|
...this.request.entity,
|
||||||
|
revision: this.revision
|
||||||
|
}),
|
||||||
|
errorStrategy: 'snackbar'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
this.enableComponentRequest.next({
|
||||||
|
id: this.request.entity.id,
|
||||||
|
uri: this.request.entity.uri,
|
||||||
|
type: ComponentType.Processor,
|
||||||
|
revision: this.client.getRevision({
|
||||||
|
...this.request.entity,
|
||||||
|
revision: this.revision
|
||||||
|
}),
|
||||||
|
errorStrategy: 'snackbar'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private getModifiedProperties(): ModifiedProperties {
|
private getModifiedProperties(): ModifiedProperties {
|
||||||
const propertyControl: AbstractControl | null = this.editProcessorForm.get('properties');
|
const propertyControl: AbstractControl | null = this.editProcessorForm.get('properties');
|
||||||
if (propertyControl && propertyControl.dirty) {
|
if (propertyControl && propertyControl.dirty) {
|
||||||
|
|
|
@ -17,12 +17,24 @@
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { SessionStorageService } from '@nifi/shared';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class Client {
|
export class Client {
|
||||||
private clientId: string = uuidv4();
|
private clientId: string;
|
||||||
|
|
||||||
|
constructor(private sessionStorage: SessionStorageService) {
|
||||||
|
let clientId = this.sessionStorage.getItem<string>('clientId');
|
||||||
|
|
||||||
|
if (clientId === null) {
|
||||||
|
clientId = uuidv4();
|
||||||
|
this.sessionStorage.setItem('clientId', clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
public getClientId(): string {
|
public getClientId(): string {
|
||||||
return this.clientId;
|
return this.clientId;
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
birdseye-control;
|
birdseye-control;
|
||||||
@use 'app/pages/flow-designer/ui/canvas/graph-controls/operation-control/operation-control.component-theme' as
|
@use 'app/pages/flow-designer/ui/canvas/graph-controls/operation-control/operation-control.component-theme' as
|
||||||
operation-control;
|
operation-control;
|
||||||
|
@use 'app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component-theme' as edit-processor;
|
||||||
@use 'app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component-theme' as flow-status;
|
@use 'app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component-theme' as flow-status;
|
||||||
@use 'app/pages/flow-designer/ui/canvas/header/new-canvas-item/new-canvas-item.component-theme' as new-canvas-item;
|
@use 'app/pages/flow-designer/ui/canvas/header/new-canvas-item/new-canvas-item.component-theme' as new-canvas-item;
|
||||||
@use 'app/pages/flow-designer/ui/canvas/header/search/search.component-theme' as search;
|
@use 'app/pages/flow-designer/ui/canvas/header/search/search.component-theme' as search;
|
||||||
|
@ -94,6 +95,7 @@ html {
|
||||||
@include navigation-control.generate-theme($m3-light-theme, $m3-light-theme-config);
|
@include navigation-control.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||||
@include operation-control.generate-theme($m3-light-theme, $m3-light-theme-config);
|
@include operation-control.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||||
@include flow-status.generate-theme($m3-light-theme, $m3-light-theme-config);
|
@include flow-status.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||||
|
@include edit-processor.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||||
@include violation-details-dialog.generate-theme($m3-light-theme, $m3-light-theme-config);
|
@include violation-details-dialog.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||||
@include new-canvas-item.generate-theme($m3-light-theme, $m3-light-theme-config);
|
@include new-canvas-item.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||||
@include search.generate-theme($m3-light-theme, $m3-light-theme-config);
|
@include search.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||||
|
@ -127,6 +129,7 @@ html {
|
||||||
@include navigation-control.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
@include navigation-control.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||||
@include operation-control.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
@include operation-control.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||||
@include flow-status.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
@include flow-status.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||||
|
@include edit-processor.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||||
@include violation-details-dialog.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
@include violation-details-dialog.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||||
@include new-canvas-item.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
@include new-canvas-item.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||||
@include search.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
@include search.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||||
|
|
|
@ -161,6 +161,7 @@
|
||||||
--mdc-outlined-button-label-text-tracking: normal;
|
--mdc-outlined-button-label-text-tracking: normal;
|
||||||
--mdc-outlined-button-label-text-weight: 400;
|
--mdc-outlined-button-label-text-weight: 400;
|
||||||
--mat-outlined-button-horizontal-padding: 15px;
|
--mat-outlined-button-horizontal-padding: 15px;
|
||||||
|
--mdc-outlined-button-container-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-tab-header {
|
.mat-mdc-tab-header {
|
||||||
|
|
|
@ -17,5 +17,6 @@
|
||||||
|
|
||||||
export * from './nifi-common.service';
|
export * from './nifi-common.service';
|
||||||
export * from './storage.service';
|
export * from './storage.service';
|
||||||
|
export * from './session-storage.service';
|
||||||
export * from './theming.service';
|
export * from './theming.service';
|
||||||
export * from './map-table-helper.service';
|
export * from './map-table-helper.service';
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SessionStorageService } from './session-storage.service';
|
||||||
|
|
||||||
|
describe('SessionStorageService', () => {
|
||||||
|
let service: SessionStorageService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(SessionStorageService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -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';
|
||||||
|
|
||||||
|
interface SessionStorageEntry<T> {
|
||||||
|
item: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class SessionStorageService {
|
||||||
|
/**
|
||||||
|
* Gets an entry for the key. The entry expiration is not checked.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
*/
|
||||||
|
private getEntry<T>(key: string): null | SessionStorageEntry<T> {
|
||||||
|
try {
|
||||||
|
// parse the entry
|
||||||
|
const item = sessionStorage.getItem(key);
|
||||||
|
if (!item) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = JSON.parse(item);
|
||||||
|
|
||||||
|
// ensure the entry is present
|
||||||
|
if (entry) {
|
||||||
|
return entry;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the specified item.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @param {object} item
|
||||||
|
*/
|
||||||
|
public setItem<T>(key: string, item: T): void {
|
||||||
|
// create the entry
|
||||||
|
const entry: SessionStorageEntry<T> = {
|
||||||
|
item
|
||||||
|
};
|
||||||
|
|
||||||
|
// store the item
|
||||||
|
sessionStorage.setItem(key, JSON.stringify(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether there is an entry for this key. This will not check the expiration. If
|
||||||
|
* the entry is expired, it will return null on a subsequent getItem invocation.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public hasItem(key: string): boolean {
|
||||||
|
return this.getEntry(key) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the item with the specified key. If an item with this key does
|
||||||
|
* not exist, null is returned. If an item exists but cannot be parsed
|
||||||
|
* or is malformed/unrecognized, null is returned.
|
||||||
|
*
|
||||||
|
* @param {type} key
|
||||||
|
*/
|
||||||
|
public getItem<T>(key: string): null | T {
|
||||||
|
const entry: SessionStorageEntry<T> | null = this.getEntry(key);
|
||||||
|
if (entry === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the entry has the specified field return its value
|
||||||
|
if (entry['item']) {
|
||||||
|
return entry['item'];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the item with the specified key.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
*/
|
||||||
|
public removeItem(key: string): void {
|
||||||
|
sessionStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue