mirror of https://github.com/apache/nifi.git
NIFI-12958: Adding support for custom UIs (#8601)
* NIFI-12958: - Adding support for custom UIs. - Running NiFi dev server at context path /nf. - Fixing link used when clicking the logo in the header. - Updating titles and icons used for editing components in Settings for better consistency. - Fixed JOLT advanced UI height. * NIFI-12958: - Fixing lint issue. * NIFI-12958: - Fixing test issue. This closes #8601
This commit is contained in:
parent
7f5680d1fe
commit
2c706f5228
|
@ -58,7 +58,7 @@
|
|||
},
|
||||
"development": {
|
||||
"buildTarget": "nifi:build:development",
|
||||
"servePath": "/nifi"
|
||||
"servePath": "/nf"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
|
|
|
@ -18,7 +18,7 @@ const target = {
|
|||
});
|
||||
},
|
||||
bypass: function (req) {
|
||||
if (req.url.startsWith('/nifi/')) {
|
||||
if (req.url.startsWith('/nf/')) {
|
||||
return req.url;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,13 +15,15 @@
|
|||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="pb-5 flex flex-col h-screen justify-between">
|
||||
<div class="flex flex-col h-screen">
|
||||
<header class="nifi-header">
|
||||
<navigation></navigation>
|
||||
</header>
|
||||
@if (frameSource) {
|
||||
<iframe class="flex-1" [src]="frameSource"></iframe>
|
||||
} @else {
|
||||
<iframe class="flex-1" src="../nifi-docs/documentation"></iframe>
|
||||
}
|
||||
<div class="p-2 flex flex-1 bg-white">
|
||||
@if (frameSource) {
|
||||
<iframe class="flex-1" [src]="frameSource"></iframe>
|
||||
} @else {
|
||||
<iframe class="flex-1" src="../nifi-docs/documentation"></iframe>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
moveComponents,
|
||||
navigateToComponent,
|
||||
navigateToControllerServicesForProcessGroup,
|
||||
navigateToAdvancedProcessorUi,
|
||||
navigateToEditComponent,
|
||||
navigateToEditCurrentProcessGroup,
|
||||
navigateToManageComponentPolicies,
|
||||
|
@ -364,6 +365,25 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
if (this.canvasUtils.canRead(selection) && this.canvasUtils.isProcessor(selection)) {
|
||||
const selectionData = selection.datum();
|
||||
return !!selectionData.component.config.customUiUrl;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
clazz: 'fa fa-cogs',
|
||||
text: 'Advanced',
|
||||
action: (selection: any) => {
|
||||
const selectionData = selection.datum();
|
||||
this.store.dispatch(
|
||||
navigateToAdvancedProcessorUi({
|
||||
id: selectionData.id
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
return this.canvasUtils.isProcessGroup(selection) || selection.empty();
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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 { ActivatedRouteSnapshot, ResolveFn } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { catchError, EMPTY, map, of, switchMap, take } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { AdvancedUiParams } from '../../../../state/shared';
|
||||
import { Client } from '../../../../service/client.service';
|
||||
import { selectService } from '../../state/controller-services/controller-services.selectors';
|
||||
import { ControllerServiceService } from '../controller-service.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { fullScreenError } from '../../../../state/error/error.actions';
|
||||
|
||||
export const controllerServiceAdvancedUiParamsResolver: ResolveFn<AdvancedUiParams> = (
|
||||
route: ActivatedRouteSnapshot
|
||||
) => {
|
||||
const store: Store<NiFiState> = inject(Store);
|
||||
const controllerServiceService: ControllerServiceService = inject(ControllerServiceService);
|
||||
const client: Client = inject(Client);
|
||||
|
||||
// getting id parameter from activated route because ngrx router store
|
||||
// is not initialized when this resolver executes
|
||||
const id: string | null = route.paramMap.get('id');
|
||||
if (!id) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
return store.select(selectService(id)).pipe(
|
||||
switchMap((service) => {
|
||||
if (service) {
|
||||
return of(service);
|
||||
} else {
|
||||
return controllerServiceService.getControllerService(id).pipe(
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
store.dispatch(
|
||||
fullScreenError({
|
||||
errorDetail: {
|
||||
title: 'Unable to Open Advanced UI',
|
||||
message: errorResponse.error
|
||||
}
|
||||
})
|
||||
);
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
}
|
||||
}),
|
||||
map((entity) => {
|
||||
const revision = client.getRevision(entity);
|
||||
|
||||
const editable = entity.status.runStatus === 'DISABLED';
|
||||
|
||||
return {
|
||||
url: entity.component.customUiUrl,
|
||||
id: entity.id,
|
||||
clientId: revision.clientId,
|
||||
revision: revision.version,
|
||||
editable,
|
||||
disconnectedNodeAcknowledged: false // TODO
|
||||
} as AdvancedUiParams;
|
||||
}),
|
||||
take(1)
|
||||
);
|
||||
};
|
|
@ -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.
|
||||
*/
|
||||
|
||||
import { ActivatedRouteSnapshot, ResolveFn } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { catchError, EMPTY, map, of, switchMap, take } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { selectProcessor } from '../../state/flow/flow.selectors';
|
||||
import { FlowService } from '../flow.service';
|
||||
import { AdvancedUiParams } from '../../../../state/shared';
|
||||
import { Client } from '../../../../service/client.service';
|
||||
import { fullScreenError } from '../../../../state/error/error.actions';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
export const processorAdvancedUiParamsResolver: ResolveFn<AdvancedUiParams> = (route: ActivatedRouteSnapshot) => {
|
||||
const store: Store<NiFiState> = inject(Store);
|
||||
const flowService: FlowService = inject(FlowService);
|
||||
const client: Client = inject(Client);
|
||||
|
||||
// getting id parameter from activated route because ngrx router store
|
||||
// is not initialized when this resolver executes
|
||||
const id: string | null = route.paramMap.get('id');
|
||||
if (!id) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
return store.select(selectProcessor(id)).pipe(
|
||||
switchMap((processor) => {
|
||||
if (processor) {
|
||||
return of(processor);
|
||||
} else {
|
||||
return flowService.getProcessor(id).pipe(
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
store.dispatch(
|
||||
fullScreenError({
|
||||
errorDetail: {
|
||||
title: 'Unable to Open Advanced UI',
|
||||
message: errorResponse.error
|
||||
}
|
||||
})
|
||||
);
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
}
|
||||
}),
|
||||
map((entity) => {
|
||||
const revision = client.getRevision(entity);
|
||||
|
||||
const editable = !(
|
||||
entity.status.aggregateSnapshot.runStatus === 'Running' ||
|
||||
entity.status.aggregateSnapshot.activeThreadCount > 0
|
||||
);
|
||||
|
||||
return {
|
||||
url: entity.component.config.customUiUrl,
|
||||
id: entity.id,
|
||||
clientId: revision.clientId,
|
||||
revision: revision.version,
|
||||
editable,
|
||||
disconnectedNodeAcknowledged: false // TODO
|
||||
} as AdvancedUiParams;
|
||||
}),
|
||||
take(1)
|
||||
);
|
||||
};
|
|
@ -72,6 +72,11 @@ export const navigateToEditService = createAction(
|
|||
props<{ id: string }>()
|
||||
);
|
||||
|
||||
export const navigateToAdvancedServiceUi = createAction(
|
||||
'[Controller Services] Navigate To Advanced Service UI',
|
||||
props<{ id: string }>()
|
||||
);
|
||||
|
||||
export const openConfigureControllerServiceDialog = createAction(
|
||||
'[Controller Services] Open Configure Controller Service Dialog',
|
||||
props<{ request: EditControllerServiceDialogRequest }>()
|
||||
|
|
|
@ -178,6 +178,19 @@ export class ControllerServicesEffects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToAdvancedServiceUi$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.navigateToAdvancedServiceUi),
|
||||
map((action) => action.id),
|
||||
concatLatestFrom(() => this.store.select(selectCurrentProcessGroupId)),
|
||||
tap(([id, processGroupId]) => {
|
||||
this.router.navigate(['/process-groups', processGroupId, 'controller-services', id, 'advanced']);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
openConfigureControllerServiceDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
|
|
@ -351,6 +351,11 @@ export const navigateToEditComponent = createAction(
|
|||
props<{ request: OpenComponentDialogRequest }>()
|
||||
);
|
||||
|
||||
export const navigateToAdvancedProcessorUi = createAction(
|
||||
`${CANVAS_PREFIX} Navigate To Advanced Processor Ui`,
|
||||
props<{ id: string }>()
|
||||
);
|
||||
|
||||
export const navigateToManageComponentPolicies = createAction(
|
||||
`${CANVAS_PREFIX} Navigate To Manage Component Policies`,
|
||||
props<{ request: NavigateToManageComponentPoliciesRequest }>()
|
||||
|
|
|
@ -898,6 +898,19 @@ export class FlowEffects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToAdvancedProcessorUi$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.navigateToAdvancedProcessorUi),
|
||||
map((action) => action.id),
|
||||
concatLatestFrom(() => this.store.select(selectCurrentProcessGroupId)),
|
||||
tap(([id, processGroupId]) => {
|
||||
this.router.navigate(['/process-groups', processGroupId, ComponentType.Processor, id, 'advanced']);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToEditCurrentProcessGroup$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
|
|
@ -18,8 +18,16 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { Canvas } from './canvas.component';
|
||||
import { AdvancedUi } from '../../../../ui/common/advanced-ui/advanced-ui.component';
|
||||
import { processorAdvancedUiParamsResolver } from '../../service/resolver/processor-advanced-ui-params.resolver';
|
||||
import { ComponentType } from '../../../../state/shared';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: `${ComponentType.Processor}/:id/advanced`,
|
||||
resolve: { advancedUiParams: processorAdvancedUiParamsResolver },
|
||||
component: AdvancedUi
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Canvas,
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
type="button"
|
||||
[disabled]="!canConfigure(selection)"
|
||||
(click)="configure(selection)">
|
||||
<i class="fa fa-gear"></i>
|
||||
<i class="fa fa-cog"></i>
|
||||
</button>
|
||||
@if (supportsManagedAuthorizer()) {
|
||||
<button
|
||||
|
|
|
@ -18,8 +18,15 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { ControllerServices } from './controller-services.component';
|
||||
import { AdvancedUi } from '../../../../ui/common/advanced-ui/advanced-ui.component';
|
||||
import { controllerServiceAdvancedUiParamsResolver } from '../../service/resolver/controller-service-advanced-ui-params.resolver';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ':id/advanced',
|
||||
resolve: { advancedUiParams: controllerServiceAdvancedUiParamsResolver },
|
||||
component: AdvancedUi
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: ControllerServices,
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
(selectControllerService)="selectControllerService($event)"
|
||||
(viewControllerServiceDocumentation)="viewControllerServiceDocumentation($event)"
|
||||
(configureControllerService)="configureControllerService($event)"
|
||||
(openAdvancedUi)="openAdvancedUi($event)"
|
||||
(enableControllerService)="enableControllerService($event)"
|
||||
(disableControllerService)="disableControllerService($event)"
|
||||
(viewStateControllerService)="viewStateControllerService($event)"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { filter, switchMap, take, tap } from 'rxjs';
|
||||
import { filter, Observable, switchMap, take, tap } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import {
|
||||
selectControllerServiceIdFromRoute,
|
||||
|
@ -29,6 +29,7 @@ import {
|
|||
import { ControllerServicesState } from '../../state/controller-services';
|
||||
import {
|
||||
loadControllerServices,
|
||||
navigateToAdvancedServiceUi,
|
||||
navigateToEditService,
|
||||
openConfigureControllerServiceDialog,
|
||||
openDisableControllerServiceDialog,
|
||||
|
@ -47,6 +48,7 @@ import { NiFiState } from '../../../../state';
|
|||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||
import { getComponentStateAndOpenDialog } from '../../../../state/component-state/component-state.actions';
|
||||
import { navigateToComponentDocumentation } from '../../../../state/documentation/documentation.actions';
|
||||
import { FlowConfiguration } from '../../../../state/flow-configuration';
|
||||
|
||||
@Component({
|
||||
selector: 'controller-services',
|
||||
|
@ -57,7 +59,9 @@ export class ControllerServices implements OnInit, OnDestroy {
|
|||
serviceState$ = this.store.select(selectControllerServicesState);
|
||||
selectedServiceId$ = this.store.select(selectControllerServiceIdFromRoute);
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration).pipe(isDefinedAndNotNull());
|
||||
flowConfiguration$: Observable<FlowConfiguration> = this.store
|
||||
.select(selectFlowConfiguration)
|
||||
.pipe(isDefinedAndNotNull());
|
||||
|
||||
private currentProcessGroupId!: string;
|
||||
|
||||
|
@ -182,6 +186,14 @@ export class ControllerServices implements OnInit, OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
openAdvancedUi(entity: ControllerServiceEntity): void {
|
||||
this.store.dispatch(
|
||||
navigateToAdvancedServiceUi({
|
||||
id: entity.id
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
enableControllerService(entity: ControllerServiceEntity): void {
|
||||
this.store.dispatch(
|
||||
openEnableControllerServiceDialog({
|
||||
|
|
|
@ -26,8 +26,27 @@ import { RegistryClients } from '../ui/registry-clients/registry-clients.compone
|
|||
import { ParameterProviders } from '../ui/parameter-providers/parameter-providers.component';
|
||||
import { authorizationGuard } from '../../../service/guard/authorization.guard';
|
||||
import { CurrentUser } from '../../../state/current-user';
|
||||
import { AdvancedUi } from '../../../ui/common/advanced-ui/advanced-ui.component';
|
||||
import { controllerServiceAdvancedUiParamsResolver } from '../service/resolver/controller-service-advanced-ui-params.resolver';
|
||||
import { reportingTaskAdvancedUiParamsResolver } from '../service/resolver/reporting-task-advanced-ui-params.resolver';
|
||||
import { parameterProviderAdvancedUiParamsResolver } from '../service/resolver/parameter-provider-advanced-ui-params.resolver';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'management-controller-services/:id/advanced',
|
||||
resolve: { advancedUiParams: controllerServiceAdvancedUiParamsResolver },
|
||||
component: AdvancedUi
|
||||
},
|
||||
{
|
||||
path: 'reporting-tasks/:id/advanced',
|
||||
resolve: { advancedUiParams: reportingTaskAdvancedUiParamsResolver },
|
||||
component: AdvancedUi
|
||||
},
|
||||
{
|
||||
path: 'parameter-providers/:id/advanced',
|
||||
resolve: { advancedUiParams: parameterProviderAdvancedUiParamsResolver },
|
||||
component: AdvancedUi
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Settings,
|
||||
|
|
|
@ -58,6 +58,13 @@ export class ManagementControllerServiceService implements ControllerServiceCrea
|
|||
});
|
||||
}
|
||||
|
||||
getControllerService(id: string): Observable<any> {
|
||||
const uiOnly: any = { uiOnly: true };
|
||||
return this.httpClient.get(`${ManagementControllerServiceService.API}/controller-services/${id}`, {
|
||||
params: uiOnly
|
||||
});
|
||||
}
|
||||
|
||||
getPropertyDescriptor(id: string, propertyName: string, sensitive: boolean): Observable<any> {
|
||||
const params: any = {
|
||||
propertyName,
|
||||
|
|
|
@ -45,6 +45,10 @@ export class ParameterProviderService implements PropertyDescriptorRetriever {
|
|||
return this.httpClient.get(`${ParameterProviderService.API}/flow/parameter-providers`);
|
||||
}
|
||||
|
||||
getParameterProvider(id: string): Observable<any> {
|
||||
return this.httpClient.get(`${ParameterProviderService.API}/parameter-providers/${id}`);
|
||||
}
|
||||
|
||||
createParameterProvider(request: CreateParameterProviderRequest) {
|
||||
return this.httpClient.post(`${ParameterProviderService.API}/controller/parameter-providers`, {
|
||||
revision: request.revision,
|
||||
|
|
|
@ -44,6 +44,10 @@ export class ReportingTaskService implements PropertyDescriptorRetriever {
|
|||
return this.httpClient.get(`${ReportingTaskService.API}/flow/reporting-tasks`);
|
||||
}
|
||||
|
||||
getReportingTask(id: string): Observable<any> {
|
||||
return this.httpClient.get(`${ReportingTaskService.API}/reporting-tasks/${id}`);
|
||||
}
|
||||
|
||||
createReportingTask(createReportingTask: CreateReportingTaskRequest): Observable<any> {
|
||||
return this.httpClient.post(`${ReportingTaskService.API}/controller/reporting-tasks`, {
|
||||
revision: createReportingTask.revision,
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 { ActivatedRouteSnapshot, ResolveFn } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { catchError, EMPTY, map, of, switchMap, take } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { AdvancedUiParams } from '../../../../state/shared';
|
||||
import { Client } from '../../../../service/client.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { fullScreenError } from '../../../../state/error/error.actions';
|
||||
import { ManagementControllerServiceService } from '../management-controller-service.service';
|
||||
import { selectService } from '../../state/management-controller-services/management-controller-services.selectors';
|
||||
|
||||
export const controllerServiceAdvancedUiParamsResolver: ResolveFn<AdvancedUiParams> = (
|
||||
route: ActivatedRouteSnapshot
|
||||
) => {
|
||||
const store: Store<NiFiState> = inject(Store);
|
||||
const managementControllerServiceService: ManagementControllerServiceService = inject(
|
||||
ManagementControllerServiceService
|
||||
);
|
||||
const client: Client = inject(Client);
|
||||
|
||||
// getting id parameter from activated route because ngrx router store
|
||||
// is not initialized when this resolver executes
|
||||
const id: string | null = route.paramMap.get('id');
|
||||
if (!id) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
return store.select(selectService(id)).pipe(
|
||||
switchMap((service) => {
|
||||
if (service) {
|
||||
return of(service);
|
||||
} else {
|
||||
return managementControllerServiceService.getControllerService(id).pipe(
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
store.dispatch(
|
||||
fullScreenError({
|
||||
errorDetail: {
|
||||
title: 'Unable to Open Advanced UI',
|
||||
message: errorResponse.error
|
||||
}
|
||||
})
|
||||
);
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
}
|
||||
}),
|
||||
map((entity) => {
|
||||
const revision = client.getRevision(entity);
|
||||
|
||||
const editable = entity.status.runStatus === 'DISABLED';
|
||||
|
||||
return {
|
||||
url: entity.component.customUiUrl,
|
||||
id: entity.id,
|
||||
clientId: revision.clientId,
|
||||
revision: revision.version,
|
||||
editable,
|
||||
disconnectedNodeAcknowledged: false // TODO
|
||||
} as AdvancedUiParams;
|
||||
}),
|
||||
take(1)
|
||||
);
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 { ActivatedRouteSnapshot, ResolveFn } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { catchError, EMPTY, map, of, switchMap, take } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { AdvancedUiParams } from '../../../../state/shared';
|
||||
import { Client } from '../../../../service/client.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { fullScreenError } from '../../../../state/error/error.actions';
|
||||
import { ParameterProviderService } from '../parameter-provider.service';
|
||||
import { selectParameterProvider } from '../../state/parameter-providers/parameter-providers.selectors';
|
||||
|
||||
export const parameterProviderAdvancedUiParamsResolver: ResolveFn<AdvancedUiParams> = (
|
||||
route: ActivatedRouteSnapshot
|
||||
) => {
|
||||
const store: Store<NiFiState> = inject(Store);
|
||||
const parameterProviderService: ParameterProviderService = inject(ParameterProviderService);
|
||||
const client: Client = inject(Client);
|
||||
|
||||
// getting id parameter from activated route because ngrx router store
|
||||
// is not initialized when this resolver executes
|
||||
const id: string | null = route.paramMap.get('id');
|
||||
if (!id) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
return store.select(selectParameterProvider(id)).pipe(
|
||||
switchMap((parameterProvider) => {
|
||||
if (parameterProvider) {
|
||||
return of(parameterProvider);
|
||||
} else {
|
||||
return parameterProviderService.getParameterProvider(id).pipe(
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
store.dispatch(
|
||||
fullScreenError({
|
||||
errorDetail: {
|
||||
title: 'Unable to Open Advanced UI',
|
||||
message: errorResponse.error
|
||||
}
|
||||
})
|
||||
);
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
}
|
||||
}),
|
||||
map((entity) => {
|
||||
const revision = client.getRevision(entity);
|
||||
|
||||
return {
|
||||
url: entity.component.customUiUrl,
|
||||
id: entity.id,
|
||||
clientId: revision.clientId,
|
||||
revision: revision.version,
|
||||
editable: true,
|
||||
disconnectedNodeAcknowledged: false // TODO
|
||||
} as AdvancedUiParams;
|
||||
}),
|
||||
take(1)
|
||||
);
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 { ActivatedRouteSnapshot, ResolveFn } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { catchError, EMPTY, map, of, switchMap, take } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { AdvancedUiParams } from '../../../../state/shared';
|
||||
import { Client } from '../../../../service/client.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { fullScreenError } from '../../../../state/error/error.actions';
|
||||
import { ReportingTaskService } from '../reporting-task.service';
|
||||
import { selectTask } from '../../state/reporting-tasks/reporting-tasks.selectors';
|
||||
|
||||
export const reportingTaskAdvancedUiParamsResolver: ResolveFn<AdvancedUiParams> = (route: ActivatedRouteSnapshot) => {
|
||||
const store: Store<NiFiState> = inject(Store);
|
||||
const reportingTaskService: ReportingTaskService = inject(ReportingTaskService);
|
||||
const client: Client = inject(Client);
|
||||
|
||||
// getting id parameter from activated route because ngrx router store
|
||||
// is not initialized when this resolver executes
|
||||
const id: string | null = route.paramMap.get('id');
|
||||
if (!id) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
return store.select(selectTask(id)).pipe(
|
||||
switchMap((reportingTask) => {
|
||||
if (reportingTask) {
|
||||
return of(reportingTask);
|
||||
} else {
|
||||
return reportingTaskService.getReportingTask(id).pipe(
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
store.dispatch(
|
||||
fullScreenError({
|
||||
errorDetail: {
|
||||
title: 'Unable to Open Advanced UI',
|
||||
message: errorResponse.error
|
||||
}
|
||||
})
|
||||
);
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
}
|
||||
}),
|
||||
map((entity) => {
|
||||
const revision = client.getRevision(entity);
|
||||
|
||||
const editable = entity.status.runStatus === 'STOPPED' || entity.status.runStatus === 'DISABLED';
|
||||
|
||||
return {
|
||||
url: entity.component.customUiUrl,
|
||||
id: entity.id,
|
||||
clientId: revision.clientId,
|
||||
revision: revision.version,
|
||||
editable,
|
||||
disconnectedNodeAcknowledged: false // TODO
|
||||
} as AdvancedUiParams;
|
||||
}),
|
||||
take(1)
|
||||
);
|
||||
};
|
|
@ -79,6 +79,11 @@ export const navigateToEditService = createAction(
|
|||
props<{ id: string }>()
|
||||
);
|
||||
|
||||
export const navigateToAdvancedServiceUi = createAction(
|
||||
'[Controller Services] Navigate To Advanced Service UI',
|
||||
props<{ id: string }>()
|
||||
);
|
||||
|
||||
export const openConfigureControllerServiceDialog = createAction(
|
||||
'[Management Controller Services] Open Configure Controller Service Dialog',
|
||||
props<{ request: EditControllerServiceDialogRequest }>()
|
||||
|
|
|
@ -171,6 +171,18 @@ export class ManagementControllerServicesEffects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToAdvancedServiceUi$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ManagementControllerServicesActions.navigateToAdvancedServiceUi),
|
||||
map((action) => action.id),
|
||||
tap((id) => {
|
||||
this.router.navigate(['/settings', 'management-controller-services', id, 'advanced']);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
openConfigureControllerServiceDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
|
|
@ -55,6 +55,7 @@ export interface ParameterProvider {
|
|||
affectedComponents: AffectedComponentEntity[];
|
||||
bundle: Bundle;
|
||||
comments: string;
|
||||
customUiUrl?: string;
|
||||
deprecated: boolean;
|
||||
descriptors: { [key: string]: PropertyDescriptor };
|
||||
extensionMissing: boolean;
|
||||
|
|
|
@ -87,6 +87,11 @@ export const navigateToEditParameterProvider = createAction(
|
|||
props<{ id: string }>()
|
||||
);
|
||||
|
||||
export const navigateToAdvancedParameterProviderUi = createAction(
|
||||
`${PARAMETER_PROVIDERS_PREFIX} Navigate To Advanced Parameter Provider UI`,
|
||||
props<{ id: string }>()
|
||||
);
|
||||
|
||||
export const navigateToFetchParameterProvider = createAction(
|
||||
`${PARAMETER_PROVIDERS_PREFIX} Navigate To Fetch Parameter Provider`,
|
||||
props<{ id: string }>()
|
||||
|
|
|
@ -237,6 +237,18 @@ export class ParameterProvidersEffects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToAdvancedParameterProviderUi$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ParameterProviderActions.navigateToAdvancedParameterProviderUi),
|
||||
map((action) => action.id),
|
||||
tap((id) => {
|
||||
this.router.navigate(['settings', 'parameter-providers', id, 'advanced']);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToFetchParameterProvider$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
|
|
@ -83,6 +83,11 @@ export const navigateToEditReportingTask = createAction(
|
|||
props<{ id: string }>()
|
||||
);
|
||||
|
||||
export const navigateToAdvancedReportingTaskUi = createAction(
|
||||
'[Reporting Tasks] Navigate To Advanced Reporting Task UI',
|
||||
props<{ id: string }>()
|
||||
);
|
||||
|
||||
export const startReportingTask = createAction(
|
||||
'[Reporting Tasks] Start Reporting Task',
|
||||
props<{ request: StartReportingTaskRequest }>()
|
||||
|
|
|
@ -215,6 +215,18 @@ export class ReportingTasksEffects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToAdvancedReportingTaskUi$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ReportingTaskActions.navigateToAdvancedReportingTaskUi),
|
||||
map((action) => action.id),
|
||||
tap((id) => {
|
||||
this.router.navigate(['/settings', 'reporting-tasks', id, 'advanced']);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
openConfigureReportingTaskDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
|
|
@ -123,7 +123,7 @@
|
|||
<td mat-cell *matCellDef="let item">
|
||||
<div class="flex items-center gap-x-3">
|
||||
@if (canConfigure(item)) {
|
||||
<div class="pointer fa fa-gear" (click)="configureClicked(item, $event)" title="Edit"></div>
|
||||
<div class="pointer fa fa-cog" (click)="configureClicked(item, $event)" title="Edit"></div>
|
||||
}
|
||||
<!-- TODO - handle read only in configure component? -->
|
||||
@if (canDisable(item)) {
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
(selectControllerService)="selectControllerService($event)"
|
||||
(viewControllerServiceDocumentation)="viewControllerServiceDocumentation($event)"
|
||||
(configureControllerService)="configureControllerService($event)"
|
||||
(openAdvancedUi)="openAdvancedUi($event)"
|
||||
(enableControllerService)="enableControllerService($event)"
|
||||
(disableControllerService)="disableControllerService($event)"
|
||||
(viewStateControllerService)="viewStateControllerService($event)"
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from '../../state/management-controller-services/management-controller-services.selectors';
|
||||
import {
|
||||
loadManagementControllerServices,
|
||||
navigateToAdvancedServiceUi,
|
||||
navigateToEditService,
|
||||
openConfigureControllerServiceDialog,
|
||||
openDisableControllerServiceDialog,
|
||||
|
@ -132,6 +133,14 @@ export class ManagementControllerServices implements OnInit, OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
openAdvancedUi(entity: ControllerServiceEntity): void {
|
||||
this.store.dispatch(
|
||||
navigateToAdvancedServiceUi({
|
||||
id: entity.id
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
enableControllerService(entity: ControllerServiceEntity): void {
|
||||
this.store.dispatch(
|
||||
openEnableControllerServiceDialog({
|
||||
|
|
|
@ -93,10 +93,16 @@
|
|||
<div class="flex items-center gap-x-3">
|
||||
@if (canConfigure(item)) {
|
||||
<div
|
||||
class="pointer fa fa-pencil"
|
||||
class="pointer fa fa-cog"
|
||||
(click)="configureClicked(item, $event)"
|
||||
title="Edit"></div>
|
||||
}
|
||||
@if (hasAdvancedUi(item)) {
|
||||
<div
|
||||
class="pointer fa fa-cogs"
|
||||
(click)="advancedClicked(item, $event)"
|
||||
title="Advanced"></div>
|
||||
}
|
||||
@if (canFetch(item)) {
|
||||
<div
|
||||
class="pointer fa fa-arrow-circle-down"
|
||||
|
|
|
@ -76,6 +76,7 @@ export class ParameterProvidersTable {
|
|||
new EventEmitter<ParameterProviderEntity>();
|
||||
@Output() configureParameterProvider: EventEmitter<ParameterProviderEntity> =
|
||||
new EventEmitter<ParameterProviderEntity>();
|
||||
@Output() openAdvancedUi: EventEmitter<ParameterProviderEntity> = new EventEmitter<ParameterProviderEntity>();
|
||||
@Output() deleteParameterProvider: EventEmitter<ParameterProviderEntity> =
|
||||
new EventEmitter<ParameterProviderEntity>();
|
||||
@Output() fetchParameterProvider: EventEmitter<ParameterProviderEntity> =
|
||||
|
@ -100,6 +101,10 @@ export class ParameterProvidersTable {
|
|||
return this.canRead(entity) && this.canWrite(entity);
|
||||
}
|
||||
|
||||
hasAdvancedUi(entity: ParameterProviderEntity): boolean {
|
||||
return this.canRead(entity) && !!entity.component.customUiUrl;
|
||||
}
|
||||
|
||||
canDelete(entity: ParameterProviderEntity): boolean {
|
||||
return (
|
||||
this.canRead(entity) &&
|
||||
|
@ -194,6 +199,11 @@ export class ParameterProvidersTable {
|
|||
this.configureParameterProvider.next(entity);
|
||||
}
|
||||
|
||||
advancedClicked(entity: ParameterProviderEntity, event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
this.openAdvancedUi.next(entity);
|
||||
}
|
||||
|
||||
fetchClicked(entity: ParameterProviderEntity, event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
this.fetchParameterProvider.next(entity);
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
[selectedParameterProviderId]="selectedParameterProviderId$ | async"
|
||||
(deleteParameterProvider)="deleteParameterProvider($event)"
|
||||
(configureParameterProvider)="openConfigureParameterProviderDialog($event)"
|
||||
(openAdvancedUi)="openAdvancedUi($event)"
|
||||
(fetchParameterProvider)="fetchParameterProviderParameters($event)"
|
||||
(viewParameterProviderDocumentation)="viewParameterProviderDocumentation($event)"
|
||||
(selectParameterProvider)="selectParameterProvider($event)"></parameter-providers-table>
|
||||
|
|
|
@ -131,6 +131,14 @@ export class ParameterProviders implements OnInit, OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
openAdvancedUi(parameterProvider: ParameterProviderEntity) {
|
||||
this.store.dispatch(
|
||||
ParameterProviderActions.navigateToAdvancedParameterProviderUi({
|
||||
id: parameterProvider.id
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
viewParameterProviderDocumentation(parameterProvider: ParameterProviderEntity): void {
|
||||
this.store.dispatch(
|
||||
navigateToComponentDocumentation({
|
||||
|
|
|
@ -102,9 +102,9 @@
|
|||
<div class="flex items-center gap-x-3">
|
||||
@if (canConfigure(item)) {
|
||||
<div
|
||||
class="pointer fa fa-pencil"
|
||||
class="pointer fa fa-cog"
|
||||
(click)="configureClicked(item, $event)"
|
||||
title="Configure"></div>
|
||||
title="Edit"></div>
|
||||
}
|
||||
@if (canDelete(item)) {
|
||||
<div class="pointer fa fa-trash" (click)="deleteClicked(item, $event)" title="Delete"></div>
|
||||
|
|
|
@ -118,10 +118,13 @@
|
|||
<div class="pointer fa fa-stop" (click)="stopClicked(item)" title="Stop"></div>
|
||||
}
|
||||
@if (canEdit(item)) {
|
||||
<div class="pointer fa fa-cog" (click)="configureClicked(item, $event)" title="Edit"></div>
|
||||
}
|
||||
@if (hasAdvancedUi(item)) {
|
||||
<div
|
||||
class="pointer fa fa-pencil"
|
||||
(click)="configureClicked(item, $event)"
|
||||
title="Edit"></div>
|
||||
class="pointer fa fa-cogs"
|
||||
(click)="advancedClicked(item, $event)"
|
||||
title="Advanced"></div>
|
||||
}
|
||||
@if (canStart(item)) {
|
||||
<div class="pointer fa fa-play" (click)="startClicked(item)" title="Start"></div>
|
||||
|
|
|
@ -54,6 +54,7 @@ export class ReportingTaskTable {
|
|||
@Output() deleteReportingTask: EventEmitter<ReportingTaskEntity> = new EventEmitter<ReportingTaskEntity>();
|
||||
@Output() startReportingTask: EventEmitter<ReportingTaskEntity> = new EventEmitter<ReportingTaskEntity>();
|
||||
@Output() configureReportingTask: EventEmitter<ReportingTaskEntity> = new EventEmitter<ReportingTaskEntity>();
|
||||
@Output() openAdvancedUi: EventEmitter<ReportingTaskEntity> = new EventEmitter<ReportingTaskEntity>();
|
||||
@Output() viewStateReportingTask: EventEmitter<ReportingTaskEntity> = new EventEmitter<ReportingTaskEntity>();
|
||||
@Output() stopReportingTask: EventEmitter<ReportingTaskEntity> = new EventEmitter<ReportingTaskEntity>();
|
||||
|
||||
|
@ -197,6 +198,15 @@ export class ReportingTaskTable {
|
|||
return this.canRead(entity) && this.canWrite(entity) && this.isStoppedOrDisabled(entity);
|
||||
}
|
||||
|
||||
hasAdvancedUi(entity: ReportingTaskEntity): boolean {
|
||||
return this.canRead(entity) && !!entity.component.customUiUrl;
|
||||
}
|
||||
|
||||
advancedClicked(entity: ReportingTaskEntity, event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
this.openAdvancedUi.next(entity);
|
||||
}
|
||||
|
||||
canStart(entity: ReportingTaskEntity): boolean {
|
||||
return this.canOperate(entity) && this.isStopped(entity) && this.isValid(entity);
|
||||
}
|
||||
|
@ -205,10 +215,6 @@ export class ReportingTaskTable {
|
|||
this.startReportingTask.next(entity);
|
||||
}
|
||||
|
||||
canConfigure(entity: ReportingTaskEntity): boolean {
|
||||
return this.canRead(entity) && this.canWrite(entity) && this.isDisabled(entity);
|
||||
}
|
||||
|
||||
canChangeVersion(entity: ReportingTaskEntity): boolean {
|
||||
return (
|
||||
(this.isDisabled(entity) || this.isStopped(entity)) &&
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
[currentUser]="currentUser"
|
||||
[flowConfiguration]="(flowConfiguration$ | async)!"
|
||||
(configureReportingTask)="configureReportingTask($event)"
|
||||
(openAdvancedUi)="openAdvancedUi($event)"
|
||||
(viewStateReportingTask)="viewStateReportingTask($event)"
|
||||
(selectReportingTask)="selectReportingTask($event)"
|
||||
(viewReportingTaskDocumentation)="viewReportingTaskDocumentation($event)"
|
||||
|
|
|
@ -35,7 +35,8 @@ import {
|
|||
resetReportingTasksState,
|
||||
startReportingTask,
|
||||
stopReportingTask,
|
||||
selectReportingTask
|
||||
selectReportingTask,
|
||||
navigateToAdvancedReportingTaskUi
|
||||
} from '../../state/reporting-tasks/reporting-tasks.actions';
|
||||
import { initialState } from '../../state/reporting-tasks/reporting-tasks.reducer';
|
||||
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
|
||||
|
@ -111,6 +112,14 @@ export class ReportingTasks implements OnInit, OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
openAdvancedUi(entity: ReportingTaskEntity): void {
|
||||
this.store.dispatch(
|
||||
navigateToAdvancedReportingTaskUi({
|
||||
id: entity.id
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
viewReportingTaskDocumentation(entity: ReportingTaskEntity): void {
|
||||
this.store.dispatch(
|
||||
navigateToComponentDocumentation({
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { routerReducer, RouterReducerState } from '@ngrx/router-store';
|
||||
import { routerReducer, RouterReducerState, DEFAULT_ROUTER_FEATURENAME } from '@ngrx/router-store';
|
||||
import { ActionReducerMap } from '@ngrx/store';
|
||||
import { CurrentUserState, currentUserFeatureKey } from './current-user';
|
||||
import { currentUserReducer } from './current-user/current-user.reducer';
|
||||
|
@ -41,7 +41,7 @@ import { clusterSummaryFeatureKey, ClusterSummaryState } from './cluster-summary
|
|||
import { clusterSummaryReducer } from './cluster-summary/cluster-summary.reducer';
|
||||
|
||||
export interface NiFiState {
|
||||
router: RouterReducerState;
|
||||
[DEFAULT_ROUTER_FEATURENAME]: RouterReducerState;
|
||||
[errorFeatureKey]: ErrorState;
|
||||
[currentUserFeatureKey]: CurrentUserState;
|
||||
[extensionTypesFeatureKey]: ExtensionTypesState;
|
||||
|
@ -56,7 +56,7 @@ export interface NiFiState {
|
|||
}
|
||||
|
||||
export const rootReducers: ActionReducerMap<NiFiState> = {
|
||||
router: routerReducer,
|
||||
[DEFAULT_ROUTER_FEATURENAME]: routerReducer,
|
||||
[errorFeatureKey]: errorReducer,
|
||||
[currentUserFeatureKey]: currentUserReducer,
|
||||
[extensionTypesFeatureKey]: extensionTypesReducer,
|
||||
|
|
|
@ -60,6 +60,15 @@ export interface EditParameterResponse {
|
|||
parameter: Parameter;
|
||||
}
|
||||
|
||||
export interface AdvancedUiParams {
|
||||
url: string;
|
||||
id: string;
|
||||
revision: number;
|
||||
clientId: string;
|
||||
editable: boolean;
|
||||
disconnectedNodeAcknowledged: boolean;
|
||||
}
|
||||
|
||||
export interface UserEntity {
|
||||
id: string;
|
||||
permissions: Permissions;
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<div class="flex flex-col h-screen">
|
||||
<header class="nifi-header">
|
||||
<navigation></navigation>
|
||||
</header>
|
||||
<div class="p-2 flex flex-1 bg-white">
|
||||
@if (frameSource) {
|
||||
<iframe class="flex-1" [src]="frameSource"></iframe>
|
||||
} @else {
|
||||
<div>Unable to open Advanced configuration UI.</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
|
@ -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 { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AdvancedUi } from './advanced-ui.component';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Component } from '@angular/core';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../../state/documentation/documentation.reducer';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
|
||||
describe('AdvancedUi', () => {
|
||||
let component: AdvancedUi;
|
||||
let fixture: ComponentFixture<AdvancedUi>;
|
||||
|
||||
@Component({
|
||||
selector: 'navigation',
|
||||
standalone: true,
|
||||
template: ''
|
||||
})
|
||||
class MockNavigation {}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AdvancedUi, HttpClientTestingModule, RouterTestingModule, MockNavigation],
|
||||
providers: [provideMockStore({ initialState })]
|
||||
});
|
||||
fixture = TestBed.createComponent(AdvancedUi);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, SecurityContext } from '@angular/core';
|
||||
import { NiFiState } from '../../../state';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { HttpParams } from '@angular/common/http';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { Navigation } from '../navigation/navigation.component';
|
||||
import { selectRouteData } from '../../../state/router/router.selectors';
|
||||
import { AdvancedUiParams, isDefinedAndNotNull } from '../../../state/shared';
|
||||
|
||||
@Component({
|
||||
selector: 'advanced-ui',
|
||||
standalone: true,
|
||||
templateUrl: './advanced-ui.component.html',
|
||||
imports: [Navigation],
|
||||
styleUrls: ['./advanced-ui.component.scss']
|
||||
})
|
||||
export class AdvancedUi {
|
||||
frameSource!: SafeResourceUrl | null;
|
||||
|
||||
constructor(
|
||||
private store: Store<NiFiState>,
|
||||
private domSanitizer: DomSanitizer
|
||||
) {
|
||||
this.store
|
||||
.select(selectRouteData)
|
||||
.pipe(takeUntilDestroyed(), isDefinedAndNotNull())
|
||||
.subscribe((data) => {
|
||||
if (data['advancedUiParams']) {
|
||||
this.frameSource = this.getFrameSource(data['advancedUiParams']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getFrameSource(params: AdvancedUiParams): SafeResourceUrl | null {
|
||||
const queryParams: string = new HttpParams()
|
||||
.set('id', params.id)
|
||||
.set('revision', params.revision)
|
||||
.set('clientId', params.clientId)
|
||||
.set('editable', params.editable)
|
||||
.set('disconnectedNodeAcknowledged', params.disconnectedNodeAcknowledged)
|
||||
.toString();
|
||||
const url = `${params.url}?${queryParams}`;
|
||||
|
||||
const sanitizedUrl = this.domSanitizer.sanitize(SecurityContext.URL, url);
|
||||
|
||||
if (sanitizedUrl) {
|
||||
return this.domSanitizer.bypassSecurityTrustResourceUrl(sanitizedUrl);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -122,14 +122,20 @@
|
|||
<div class="flex items-center gap-x-3">
|
||||
@if (canConfigure(item)) {
|
||||
<div
|
||||
class="pointer fa fa-gear"
|
||||
class="pointer fa fa-cog"
|
||||
(click)="configureClicked(item, $event)"
|
||||
title="Configure"></div>
|
||||
title="Edit"></div>
|
||||
}
|
||||
@if (hasAdvancedUi(item)) {
|
||||
<div
|
||||
class="pointer fa fa-cogs"
|
||||
(click)="advancedClicked(item, $event)"
|
||||
title="Advanced"></div>
|
||||
}
|
||||
<!-- TODO - handle read only in configure component? -->
|
||||
@if (canDisable(item)) {
|
||||
<div
|
||||
class="pointer fa icon icon-enable-false"
|
||||
class="pointer icon icon-enable-false"
|
||||
(click)="disableClicked(item)"
|
||||
title="Disable"></div>
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ export class ControllerServiceTable {
|
|||
new EventEmitter<ControllerServiceEntity>();
|
||||
@Output() configureControllerService: EventEmitter<ControllerServiceEntity> =
|
||||
new EventEmitter<ControllerServiceEntity>();
|
||||
@Output() openAdvancedUi: EventEmitter<ControllerServiceEntity> = new EventEmitter<ControllerServiceEntity>();
|
||||
@Output() enableControllerService: EventEmitter<ControllerServiceEntity> =
|
||||
new EventEmitter<ControllerServiceEntity>();
|
||||
@Output() disableControllerService: EventEmitter<ControllerServiceEntity> =
|
||||
|
@ -220,6 +221,15 @@ export class ControllerServiceTable {
|
|||
this.configureControllerService.next(entity);
|
||||
}
|
||||
|
||||
hasAdvancedUi(entity: ControllerServiceEntity): boolean {
|
||||
return this.canRead(entity) && !!entity.component.customUiUrl;
|
||||
}
|
||||
|
||||
advancedClicked(entity: ControllerServiceEntity, event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
this.openAdvancedUi.next(entity);
|
||||
}
|
||||
|
||||
canEnable(entity: ControllerServiceEntity): boolean {
|
||||
const userAuthorized: boolean = this.canRead(entity) && this.canOperate(entity);
|
||||
return userAuthorized && this.isDisabled(entity) && entity.status.validationStatus === 'VALID';
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
priority
|
||||
alt="NiFi Logo"
|
||||
class="pointer"
|
||||
[routerLink]="['/']" />
|
||||
[routerLink]="getCanvasLink()" />
|
||||
</div>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
|
|
|
@ -40,7 +40,7 @@ div.code-mirror-editor.trans {
|
|||
|
||||
.CodeMirror {
|
||||
border: 1px solid #eee;
|
||||
height: 40vh;
|
||||
height: 30vh;
|
||||
}
|
||||
|
||||
.info {
|
||||
|
@ -65,6 +65,7 @@ div.scrollable{
|
|||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 22px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#addButton {
|
||||
|
|
Loading…
Reference in New Issue