mirror of https://github.com/apache/nifi.git
NIFI-12486: Registry Clients (#8142)
* NIFI-12486: - Registry Clients. - General authorization guard. - Additional authorization checks in the existing Settings tabs. * NIFI-12486: - Adding authorization guard to /counters. * NIFI-12486: - Enabling some debug build out to attempt to track down a sporadic build failure. * NIFI-12486: - Addressing review feedback. * NIFI-12486: - Fixing unit test and running prettier. This closes #8142
This commit is contained in:
parent
76613a0ed4
commit
b0f30d6860
|
@ -16,7 +16,12 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.web.api.entity;
|
package org.apache.nifi.web.api.entity;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import org.apache.nifi.web.api.dto.util.TimeAdapter;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,6 +30,7 @@ import java.util.Set;
|
||||||
@XmlRootElement(name = "registryClientsEntity")
|
@XmlRootElement(name = "registryClientsEntity")
|
||||||
public class FlowRegistryClientsEntity extends Entity {
|
public class FlowRegistryClientsEntity extends Entity {
|
||||||
|
|
||||||
|
private Date currentTime;
|
||||||
private Set<FlowRegistryClientEntity> registries;
|
private Set<FlowRegistryClientEntity> registries;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,4 +44,19 @@ public class FlowRegistryClientsEntity extends Entity {
|
||||||
this.registries = registries;
|
this.registries = registries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return current time on the server
|
||||||
|
*/
|
||||||
|
@XmlJavaTypeAdapter(TimeAdapter.class)
|
||||||
|
@ApiModelProperty(
|
||||||
|
value = "The current time on the system.",
|
||||||
|
dataType = "string"
|
||||||
|
)
|
||||||
|
public Date getCurrentTime() {
|
||||||
|
return currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentTime(Date currentTime) {
|
||||||
|
this.currentTime = currentTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1483,6 +1483,7 @@ public class ControllerResource extends ApplicationResource {
|
||||||
|
|
||||||
final Set<FlowRegistryClientEntity> flowRegistryClients = serviceFacade.getRegistryClients();
|
final Set<FlowRegistryClientEntity> flowRegistryClients = serviceFacade.getRegistryClients();
|
||||||
final FlowRegistryClientsEntity flowRegistryClientEntities = new FlowRegistryClientsEntity();
|
final FlowRegistryClientsEntity flowRegistryClientEntities = new FlowRegistryClientsEntity();
|
||||||
|
flowRegistryClientEntities.setCurrentTime(new Date());
|
||||||
flowRegistryClientEntities.setRegistries(flowRegistryClients);
|
flowRegistryClientEntities.setRegistries(flowRegistryClients);
|
||||||
|
|
||||||
return generateOkResponse(populateRemainingRegistryClientEntityContent(flowRegistryClientEntities)).build();
|
return generateOkResponse(populateRemainingRegistryClientEntityContent(flowRegistryClientEntities)).build();
|
||||||
|
|
|
@ -1838,6 +1838,7 @@ public class FlowResource extends ApplicationResource {
|
||||||
|
|
||||||
final Set<FlowRegistryClientEntity> registryClients = serviceFacade.getRegistryClientsForUser();
|
final Set<FlowRegistryClientEntity> registryClients = serviceFacade.getRegistryClientsForUser();
|
||||||
final FlowRegistryClientsEntity registryClientEntities = new FlowRegistryClientsEntity();
|
final FlowRegistryClientsEntity registryClientEntities = new FlowRegistryClientsEntity();
|
||||||
|
registryClientEntities.setCurrentTime(new Date());
|
||||||
registryClientEntities.setRegistries(registryClients);
|
registryClientEntities.setRegistries(registryClients);
|
||||||
|
|
||||||
return generateOkResponse(populateRemainingRegistryClientEntityContent(registryClientEntities)).build();
|
return generateOkResponse(populateRemainingRegistryClientEntityContent(registryClientEntities)).build();
|
||||||
|
|
|
@ -37,7 +37,9 @@
|
||||||
"maximumError": "3mb"
|
"maximumError": "3mb"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputHashing": "all"
|
"outputHashing": "all",
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"optimization": true
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"buildOptimizer": false,
|
"buildOptimizer": false,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build",
|
"build": "ng build --verbose",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
"test": "ng test --karma-config=karma.conf.js --watch=false",
|
"test": "ng test --karma-config=karma.conf.js --watch=false",
|
||||||
"prettier": "prettier --config .prettierrc . --check",
|
"prettier": "prettier --config .prettierrc . --check",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { authGuard } from './service/guard/auth.guard';
|
import { authenticationGuard } from './service/guard/authentication.guard';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -26,17 +26,17 @@ const routes: Routes = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
canMatch: [authGuard],
|
canMatch: [authenticationGuard],
|
||||||
loadChildren: () => import('./pages/settings/feature/settings.module').then((m) => m.SettingsModule)
|
loadChildren: () => import('./pages/settings/feature/settings.module').then((m) => m.SettingsModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'provenance',
|
path: 'provenance',
|
||||||
canMatch: [authGuard],
|
canMatch: [authenticationGuard],
|
||||||
loadChildren: () => import('./pages/provenance/feature/provenance.module').then((m) => m.ProvenanceModule)
|
loadChildren: () => import('./pages/provenance/feature/provenance.module').then((m) => m.ProvenanceModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'parameter-contexts',
|
path: 'parameter-contexts',
|
||||||
canMatch: [authGuard],
|
canMatch: [authenticationGuard],
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./pages/parameter-contexts/feature/parameter-contexts.module').then(
|
import('./pages/parameter-contexts/feature/parameter-contexts.module').then(
|
||||||
(m) => m.ParameterContextsModule
|
(m) => m.ParameterContextsModule
|
||||||
|
@ -44,12 +44,12 @@ const routes: Routes = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'counters',
|
path: 'counters',
|
||||||
canMatch: [authGuard],
|
canMatch: [authenticationGuard],
|
||||||
loadChildren: () => import('./pages/counters/feature/counters.module').then((m) => m.CountersModule)
|
loadChildren: () => import('./pages/counters/feature/counters.module').then((m) => m.CountersModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
canMatch: [authGuard],
|
canMatch: [authenticationGuard],
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./pages/flow-designer/feature/flow-designer.module').then((m) => m.FlowDesignerModule)
|
import('./pages/flow-designer/feature/flow-designer.module').then((m) => m.FlowDesignerModule)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,14 @@
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Counters } from './counters.component';
|
import { Counters } from './counters.component';
|
||||||
|
import { authorizationGuard } from '../../../service/guard/authorization.guard';
|
||||||
|
import { User } from '../../../state/user';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: Counters
|
component: Counters,
|
||||||
|
canMatch: [authorizationGuard((user: User) => user.countersPermissions.canRead)]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
controllerServicesApiError,
|
controllerServicesApiError,
|
||||||
createControllerService,
|
createControllerService,
|
||||||
createControllerServiceSuccess,
|
createControllerServiceSuccess,
|
||||||
|
deleteControllerService,
|
||||||
deleteControllerServiceSuccess,
|
deleteControllerServiceSuccess,
|
||||||
inlineCreateControllerServiceSuccess,
|
inlineCreateControllerServiceSuccess,
|
||||||
loadControllerServices,
|
loadControllerServices,
|
||||||
|
@ -72,7 +73,7 @@ export const controllerServicesReducer = createReducer(
|
||||||
error,
|
error,
|
||||||
status: 'error' as const
|
status: 'error' as const
|
||||||
})),
|
})),
|
||||||
on(createControllerService, (state, { request }) => ({
|
on(createControllerService, configureControllerService, deleteControllerService, (state, { request }) => ({
|
||||||
...state,
|
...state,
|
||||||
saving: true
|
saving: true
|
||||||
})),
|
})),
|
||||||
|
@ -87,10 +88,6 @@ export const controllerServicesReducer = createReducer(
|
||||||
draftState.controllerServices.push(response.controllerService);
|
draftState.controllerServices.push(response.controllerService);
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
on(configureControllerService, (state, { request }) => ({
|
|
||||||
...state,
|
|
||||||
saving: true
|
|
||||||
})),
|
|
||||||
on(configureControllerServiceSuccess, (state, { response }) => {
|
on(configureControllerServiceSuccess, (state, { response }) => {
|
||||||
return produce(state, (draftState) => {
|
return produce(state, (draftState) => {
|
||||||
const componentIndex: number = draftState.controllerServices.findIndex((f: any) => response.id === f.id);
|
const componentIndex: number = draftState.controllerServices.findIndex((f: any) => response.id === f.id);
|
||||||
|
@ -108,6 +105,7 @@ export const controllerServicesReducer = createReducer(
|
||||||
if (componentIndex > -1) {
|
if (componentIndex > -1) {
|
||||||
draftState.controllerServices.splice(componentIndex, 1);
|
draftState.controllerServices.splice(componentIndex, 1);
|
||||||
}
|
}
|
||||||
|
draftState.saving = false;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -90,7 +90,11 @@
|
||||||
Data Provenance
|
Data Provenance
|
||||||
</button>
|
</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<button mat-menu-item class="global-menu-item" [routerLink]="['/settings']">
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
class="global-menu-item"
|
||||||
|
[routerLink]="['/settings']"
|
||||||
|
[disabled]="!user.controllerPermissions.canRead">
|
||||||
<i class="fa fa-fw fa-wrench mr-2"></i>
|
<i class="fa fa-fw fa-wrench mr-2"></i>
|
||||||
Controller Settings
|
Controller Settings
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -116,7 +116,10 @@ export class ProvenanceEventTable implements AfterViewInit {
|
||||||
totalCount: number = 0;
|
totalCount: number = 0;
|
||||||
filteredCount: number = 0;
|
filteredCount: number = 0;
|
||||||
|
|
||||||
constructor(private formBuilder: FormBuilder) {
|
constructor(
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private nifiCommon: NiFiCommon
|
||||||
|
) {
|
||||||
this.filterForm = this.formBuilder.group({ filterTerm: '', filterColumn: this.filterColumnOptions[0] });
|
this.filterForm = this.formBuilder.group({ filterTerm: '', filterColumn: this.filterColumnOptions[0] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,35 +148,34 @@ export class ProvenanceEventTable implements AfterViewInit {
|
||||||
return data.sort((a, b) => {
|
return data.sort((a, b) => {
|
||||||
const isAsc = sort.direction === 'asc';
|
const isAsc = sort.direction === 'asc';
|
||||||
|
|
||||||
|
let retVal: number = 0;
|
||||||
switch (sort.active) {
|
switch (sort.active) {
|
||||||
case 'eventTime':
|
case 'eventTime':
|
||||||
// event ideas are increasing, so we can use this simple number for sorting purposes
|
// event ideas are increasing, so we can use this simple number for sorting purposes
|
||||||
// since we don't surface the timestamp as millis
|
// since we don't surface the timestamp as millis
|
||||||
return (a.eventId - b.eventId) * (isAsc ? 1 : -1);
|
retVal = this.nifiCommon.compareNumber(a.eventId, b.eventId);
|
||||||
|
break;
|
||||||
case 'eventType':
|
case 'eventType':
|
||||||
return this.compare(a.eventType, b.eventType, isAsc);
|
retVal = this.nifiCommon.compareString(a.eventType, b.eventType);
|
||||||
|
break;
|
||||||
case 'flowFileUuid':
|
case 'flowFileUuid':
|
||||||
return this.compare(a.flowFileUuid, b.flowFileUuid, isAsc);
|
retVal = this.nifiCommon.compareString(a.flowFileUuid, b.flowFileUuid);
|
||||||
|
break;
|
||||||
case 'fileSize':
|
case 'fileSize':
|
||||||
return (a.fileSizeBytes - b.fileSizeBytes) * (isAsc ? 1 : -1);
|
retVal = this.nifiCommon.compareNumber(a.fileSizeBytes, b.fileSizeBytes);
|
||||||
|
break;
|
||||||
case 'componentName':
|
case 'componentName':
|
||||||
return this.compare(a.componentName, b.componentName, isAsc);
|
retVal = this.nifiCommon.compareString(a.componentName, b.componentName);
|
||||||
|
break;
|
||||||
case 'componentType':
|
case 'componentType':
|
||||||
return this.compare(a.componentType, b.componentType, isAsc);
|
retVal = this.nifiCommon.compareString(a.componentType, b.componentType);
|
||||||
default:
|
break;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return retVal * (isAsc ? 1 : -1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private compare(a: string, b: string, isAsc: boolean): number {
|
|
||||||
if (a === b) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
applyFilter(filterTerm: string, filterColumn: string) {
|
applyFilter(filterTerm: string, filterColumn: string) {
|
||||||
this.dataSource.filter = `${filterTerm}|${filterColumn}`;
|
this.dataSource.filter = `${filterTerm}|${filterColumn}`;
|
||||||
this.filteredCount = this.dataSource.filteredData.length;
|
this.filteredCount = this.dataSource.filteredData.length;
|
||||||
|
|
|
@ -24,11 +24,14 @@ import { ReportingTasks } from '../ui/reporting-tasks/reporting-tasks.component'
|
||||||
import { FlowAnalysisRules } from '../ui/flow-analysis-rules/flow-analysis-rules.component';
|
import { FlowAnalysisRules } from '../ui/flow-analysis-rules/flow-analysis-rules.component';
|
||||||
import { RegistryClients } from '../ui/registry-clients/registry-clients.component';
|
import { RegistryClients } from '../ui/registry-clients/registry-clients.component';
|
||||||
import { ParameterProviders } from '../ui/parameter-providers/parameter-providers.component';
|
import { ParameterProviders } from '../ui/parameter-providers/parameter-providers.component';
|
||||||
|
import { authorizationGuard } from '../../../service/guard/authorization.guard';
|
||||||
|
import { User } from '../../../state/user';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: Settings,
|
component: Settings,
|
||||||
|
canMatch: [authorizationGuard((user: User) => user.controllerPermissions.canRead)],
|
||||||
children: [
|
children: [
|
||||||
{ path: '', pathMatch: 'full', redirectTo: 'general' },
|
{ path: '', pathMatch: 'full', redirectTo: 'general' },
|
||||||
{ path: 'general', component: General },
|
{ path: 'general', component: General },
|
||||||
|
@ -59,7 +62,22 @@ const routes: Routes = [
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ path: 'flow-analysis-rules', component: FlowAnalysisRules },
|
{ path: 'flow-analysis-rules', component: FlowAnalysisRules },
|
||||||
{ path: 'registry-clients', component: RegistryClients },
|
{
|
||||||
|
path: 'registry-clients',
|
||||||
|
component: RegistryClients,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: ':id',
|
||||||
|
component: RegistryClients,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'edit',
|
||||||
|
component: RegistryClients
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{ path: 'parameter-providers', component: ParameterProviders }
|
{ path: 'parameter-providers', component: ParameterProviders }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { RegistryClientsModule } from '../ui/registry-clients/registry-clients.m
|
||||||
import { ReportingTasksModule } from '../ui/reporting-tasks/reporting-tasks.module';
|
import { ReportingTasksModule } from '../ui/reporting-tasks/reporting-tasks.module';
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
import { ReportingTasksEffects } from '../state/reporting-tasks/reporting-tasks.effects';
|
import { ReportingTasksEffects } from '../state/reporting-tasks/reporting-tasks.effects';
|
||||||
|
import { RegistryClientsEffects } from '../state/registry-clients/registry-clients.effects';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [Settings],
|
declarations: [Settings],
|
||||||
|
@ -46,7 +47,12 @@ import { ReportingTasksEffects } from '../state/reporting-tasks/reporting-tasks.
|
||||||
ReportingTasksModule,
|
ReportingTasksModule,
|
||||||
SettingsRoutingModule,
|
SettingsRoutingModule,
|
||||||
StoreModule.forFeature(settingsFeatureKey, reducers),
|
StoreModule.forFeature(settingsFeatureKey, reducers),
|
||||||
EffectsModule.forFeature(GeneralEffects, ManagementControllerServicesEffects, ReportingTasksEffects),
|
EffectsModule.forFeature(
|
||||||
|
GeneralEffects,
|
||||||
|
ManagementControllerServicesEffects,
|
||||||
|
ReportingTasksEffects,
|
||||||
|
RegistryClientsEffects
|
||||||
|
),
|
||||||
MatTabsModule
|
MatTabsModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 { Injectable } from '@angular/core';
|
||||||
|
import { Observable, throwError } from 'rxjs';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Client } from '../../../service/client.service';
|
||||||
|
import { NiFiCommon } from '../../../service/nifi-common.service';
|
||||||
|
import {
|
||||||
|
CreateRegistryClientRequest,
|
||||||
|
DeleteRegistryClientRequest,
|
||||||
|
EditRegistryClientRequest,
|
||||||
|
RegistryClientEntity
|
||||||
|
} from '../state/registry-clients';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class RegistryClientService {
|
||||||
|
private static readonly API: string = '../nifi-api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
|
||||||
|
* does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
|
||||||
|
*
|
||||||
|
* https://stackoverflow.com/a/59586462
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private stripProtocol(url: string): string {
|
||||||
|
return this.nifiCommon.substringAfterFirst(url, ':');
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private httpClient: HttpClient,
|
||||||
|
private client: Client,
|
||||||
|
private nifiCommon: NiFiCommon
|
||||||
|
) {}
|
||||||
|
|
||||||
|
getRegistryClients(): Observable<any> {
|
||||||
|
return this.httpClient.get(`${RegistryClientService.API}/controller/registry-clients`);
|
||||||
|
}
|
||||||
|
|
||||||
|
createRegistryClient(createReportingTask: CreateRegistryClientRequest): Observable<any> {
|
||||||
|
return this.httpClient.post(`${RegistryClientService.API}/controller/registry-clients`, createReportingTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPropertyDescriptor(id: string, propertyName: string, sensitive: boolean): Observable<any> {
|
||||||
|
const params: any = {
|
||||||
|
propertyName,
|
||||||
|
sensitive
|
||||||
|
};
|
||||||
|
return this.httpClient.get(`${RegistryClientService.API}/controller/registry-clients/${id}/descriptors`, {
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRegistryClient(request: EditRegistryClientRequest): Observable<any> {
|
||||||
|
return this.httpClient.put(this.stripProtocol(request.uri), request.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRegistryClient(deleteRegistryClient: DeleteRegistryClientRequest): Observable<any> {
|
||||||
|
const entity: RegistryClientEntity = deleteRegistryClient.registryClient;
|
||||||
|
const revision: any = this.client.getRevision(entity);
|
||||||
|
return this.httpClient.delete(this.stripProtocol(entity.uri), { params: revision });
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,10 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
|
||||||
Canvas Positioning/Transforms
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Action, combineReducers, createFeatureSelector } from '@ngrx/store';
|
import { Action, combineReducers, createFeatureSelector } from '@ngrx/store';
|
||||||
import { GeneralState, generalFeatureKey } from './general';
|
import { GeneralState, generalFeatureKey } from './general';
|
||||||
import { generalReducer } from './general/general.reducer';
|
import { generalReducer } from './general/general.reducer';
|
||||||
|
@ -29,6 +25,8 @@ import {
|
||||||
import { managementControllerServicesReducer } from './management-controller-services/management-controller-services.reducer';
|
import { managementControllerServicesReducer } from './management-controller-services/management-controller-services.reducer';
|
||||||
import { reportingTasksFeatureKey, ReportingTasksState } from './reporting-tasks';
|
import { reportingTasksFeatureKey, ReportingTasksState } from './reporting-tasks';
|
||||||
import { reportingTasksReducer } from './reporting-tasks/reporting-tasks.reducer';
|
import { reportingTasksReducer } from './reporting-tasks/reporting-tasks.reducer';
|
||||||
|
import { registryClientsFeatureKey, RegistryClientsState } from './registry-clients';
|
||||||
|
import { registryClientsReducer } from './registry-clients/registry-clients.reducer';
|
||||||
|
|
||||||
export const settingsFeatureKey = 'settings';
|
export const settingsFeatureKey = 'settings';
|
||||||
|
|
||||||
|
@ -36,13 +34,15 @@ export interface SettingsState {
|
||||||
[generalFeatureKey]: GeneralState;
|
[generalFeatureKey]: GeneralState;
|
||||||
[managementControllerServicesFeatureKey]: ManagementControllerServicesState;
|
[managementControllerServicesFeatureKey]: ManagementControllerServicesState;
|
||||||
[reportingTasksFeatureKey]: ReportingTasksState;
|
[reportingTasksFeatureKey]: ReportingTasksState;
|
||||||
|
[registryClientsFeatureKey]: RegistryClientsState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reducers(state: SettingsState | undefined, action: Action) {
|
export function reducers(state: SettingsState | undefined, action: Action) {
|
||||||
return combineReducers({
|
return combineReducers({
|
||||||
[generalFeatureKey]: generalReducer,
|
[generalFeatureKey]: generalReducer,
|
||||||
[managementControllerServicesFeatureKey]: managementControllerServicesReducer,
|
[managementControllerServicesFeatureKey]: managementControllerServicesReducer,
|
||||||
[reportingTasksFeatureKey]: reportingTasksReducer
|
[reportingTasksFeatureKey]: reportingTasksReducer,
|
||||||
|
[registryClientsFeatureKey]: registryClientsReducer
|
||||||
})(state, action);
|
})(state, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
import * as ManagementControllerServicesActions from './management-controller-services.actions';
|
import * as ManagementControllerServicesActions from './management-controller-services.actions';
|
||||||
import { catchError, from, map, NEVER, Observable, of, switchMap, take, tap, withLatestFrom } from 'rxjs';
|
import { catchError, from, map, NEVER, Observable, of, switchMap, take, takeUntil, tap, withLatestFrom } from 'rxjs';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
|
import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
|
@ -139,15 +139,23 @@ export class ManagementControllerServicesEffects {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
createControllerServiceSuccess$ = createEffect(
|
createControllerServiceSuccess$ = createEffect(() =>
|
||||||
() =>
|
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(ManagementControllerServicesActions.createControllerServiceSuccess),
|
ofType(ManagementControllerServicesActions.createControllerServiceSuccess),
|
||||||
|
map((action) => action.response),
|
||||||
tap(() => {
|
tap(() => {
|
||||||
this.dialog.closeAll();
|
this.dialog.closeAll();
|
||||||
|
}),
|
||||||
|
switchMap((response) =>
|
||||||
|
of(
|
||||||
|
ManagementControllerServicesActions.selectControllerService({
|
||||||
|
request: {
|
||||||
|
id: response.controllerService.id
|
||||||
|
}
|
||||||
})
|
})
|
||||||
),
|
)
|
||||||
{ dispatch: false }
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
navigateToEditService$ = createEffect(
|
navigateToEditService$ = createEffect(
|
||||||
|
@ -294,7 +302,7 @@ export class ManagementControllerServicesEffects {
|
||||||
};
|
};
|
||||||
|
|
||||||
editDialogReference.componentInstance.editControllerService
|
editDialogReference.componentInstance.editControllerService
|
||||||
.pipe(take(1))
|
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||||
.subscribe((payload: any) => {
|
.subscribe((payload: any) => {
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
ManagementControllerServicesActions.configureControllerService({
|
ManagementControllerServicesActions.configureControllerService({
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
configureControllerServiceSuccess,
|
configureControllerServiceSuccess,
|
||||||
createControllerService,
|
createControllerService,
|
||||||
createControllerServiceSuccess,
|
createControllerServiceSuccess,
|
||||||
|
deleteControllerService,
|
||||||
deleteControllerServiceSuccess,
|
deleteControllerServiceSuccess,
|
||||||
inlineCreateControllerServiceSuccess,
|
inlineCreateControllerServiceSuccess,
|
||||||
loadManagementControllerServices,
|
loadManagementControllerServices,
|
||||||
|
@ -57,7 +58,7 @@ export const managementControllerServicesReducer = createReducer(
|
||||||
error,
|
error,
|
||||||
status: 'error' as const
|
status: 'error' as const
|
||||||
})),
|
})),
|
||||||
on(createControllerService, (state, { request }) => ({
|
on(createControllerService, configureControllerService, deleteControllerService, (state, { request }) => ({
|
||||||
...state,
|
...state,
|
||||||
saving: true
|
saving: true
|
||||||
})),
|
})),
|
||||||
|
@ -72,10 +73,6 @@ export const managementControllerServicesReducer = createReducer(
|
||||||
draftState.controllerServices.push(response.controllerService);
|
draftState.controllerServices.push(response.controllerService);
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
on(configureControllerService, (state, { request }) => ({
|
|
||||||
...state,
|
|
||||||
saving: true
|
|
||||||
})),
|
|
||||||
on(configureControllerServiceSuccess, (state, { response }) => {
|
on(configureControllerServiceSuccess, (state, { response }) => {
|
||||||
return produce(state, (draftState) => {
|
return produce(state, (draftState) => {
|
||||||
const componentIndex: number = draftState.controllerServices.findIndex((f: any) => response.id === f.id);
|
const componentIndex: number = draftState.controllerServices.findIndex((f: any) => response.id === f.id);
|
||||||
|
@ -93,6 +90,7 @@ export const managementControllerServicesReducer = createReducer(
|
||||||
if (componentIndex > -1) {
|
if (componentIndex > -1) {
|
||||||
draftState.controllerServices.splice(componentIndex, 1);
|
draftState.controllerServices.splice(componentIndex, 1);
|
||||||
}
|
}
|
||||||
|
draftState.saving = false;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* 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 { BulletinEntity, DocumentedType, Permissions, Revision } from '../../../../state/shared';
|
||||||
|
|
||||||
|
export const registryClientsFeatureKey = 'registryClients';
|
||||||
|
|
||||||
|
export interface CreateRegistryClientDialogRequest {
|
||||||
|
registryClientTypes: DocumentedType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoadRegistryClientsResponse {
|
||||||
|
registryClients: RegistryClientEntity[];
|
||||||
|
loadedTimestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateRegistryClientRequest {
|
||||||
|
revision: Revision;
|
||||||
|
component: {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateRegistryClientSuccess {
|
||||||
|
registryClient: RegistryClientEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditRegistryClientDialogRequest {
|
||||||
|
registryClient: RegistryClientEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditRegistryClientRequest {
|
||||||
|
id: string;
|
||||||
|
uri: string;
|
||||||
|
payload: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditRegistryClientRequestSuccess {
|
||||||
|
id: string;
|
||||||
|
registryClient: RegistryClientEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteRegistryClientRequest {
|
||||||
|
registryClient: RegistryClientEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteRegistryClientSuccess {
|
||||||
|
registryClient: RegistryClientEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectRegistryClientRequest {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegistryClientEntity {
|
||||||
|
permissions: Permissions;
|
||||||
|
operatePermissions?: Permissions;
|
||||||
|
revision: Revision;
|
||||||
|
bulletins?: BulletinEntity[];
|
||||||
|
id: string;
|
||||||
|
uri: string;
|
||||||
|
component: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegistryClientsState {
|
||||||
|
registryClients: RegistryClientEntity[];
|
||||||
|
saving: boolean;
|
||||||
|
loadedTimestamp: string;
|
||||||
|
error: string | null;
|
||||||
|
status: 'pending' | 'loading' | 'error' | 'success';
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createAction, props } from '@ngrx/store';
|
||||||
|
import {
|
||||||
|
CreateRegistryClientRequest,
|
||||||
|
CreateRegistryClientSuccess,
|
||||||
|
DeleteRegistryClientRequest,
|
||||||
|
DeleteRegistryClientSuccess,
|
||||||
|
EditRegistryClientDialogRequest,
|
||||||
|
EditRegistryClientRequest,
|
||||||
|
EditRegistryClientRequestSuccess,
|
||||||
|
LoadRegistryClientsResponse,
|
||||||
|
SelectRegistryClientRequest
|
||||||
|
} from './index';
|
||||||
|
|
||||||
|
export const loadRegistryClients = createAction('[Registry Clients] Load Registry Clients');
|
||||||
|
|
||||||
|
export const loadRegistryClientsSuccess = createAction(
|
||||||
|
'[Registry Clients] Load Registry Clients Success',
|
||||||
|
props<{ response: LoadRegistryClientsResponse }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const registryClientsApiError = createAction(
|
||||||
|
'[Registry Clients] Load Registry Clients Error',
|
||||||
|
props<{ error: string }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const openNewRegistryClientDialog = createAction('[Registry Clients] Open New Registry Client Dialog');
|
||||||
|
|
||||||
|
export const createRegistryClient = createAction(
|
||||||
|
'[Registry Clients] Create Registry Client',
|
||||||
|
props<{ request: CreateRegistryClientRequest }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const createRegistryClientSuccess = createAction(
|
||||||
|
'[Registry Clients] Create Registry Client Success',
|
||||||
|
props<{ response: CreateRegistryClientSuccess }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const navigateToEditRegistryClient = createAction(
|
||||||
|
'[Registry Clients] Navigate To Edit Registry Client',
|
||||||
|
props<{ id: string }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const openConfigureRegistryClientDialog = createAction(
|
||||||
|
'[Registry Clients] Open Configure Registry Client Dialog',
|
||||||
|
props<{ request: EditRegistryClientDialogRequest }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const configureRegistryClient = createAction(
|
||||||
|
'[Registry Clients] Configure Registry Client',
|
||||||
|
props<{ request: EditRegistryClientRequest }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const configureRegistryClientSuccess = createAction(
|
||||||
|
'[Registry Clients] Configure Registry Client Success',
|
||||||
|
props<{ response: EditRegistryClientRequestSuccess }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const promptRegistryClientDeletion = createAction(
|
||||||
|
'[Registry Clients] Prompt Registry Client Deletion',
|
||||||
|
props<{ request: DeleteRegistryClientRequest }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteRegistryClient = createAction(
|
||||||
|
'[Registry Clients] Delete Registry Client',
|
||||||
|
props<{ request: DeleteRegistryClientRequest }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteRegistryClientSuccess = createAction(
|
||||||
|
'[Registry Clients] Delete Registry Client Success',
|
||||||
|
props<{ response: DeleteRegistryClientSuccess }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectClient = createAction(
|
||||||
|
'[Registry Clients] Select Registry Client',
|
||||||
|
props<{ request: SelectRegistryClientRequest }>()
|
||||||
|
);
|
|
@ -0,0 +1,422 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import * as RegistryClientsActions from './registry-clients.actions';
|
||||||
|
import { catchError, from, map, NEVER, Observable, of, switchMap, take, takeUntil, tap, withLatestFrom } from 'rxjs';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { NiFiState } from '../../../../state';
|
||||||
|
import { selectRegistryClientTypes } from '../../../../state/extension-types/extension-types.selectors';
|
||||||
|
import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { RegistryClientService } from '../../service/registry-client.service';
|
||||||
|
import { CreateRegistryClient } from '../../ui/registry-clients/create-registry-client/create-registry-client.component';
|
||||||
|
import { selectSaving } from './registry-clients.selectors';
|
||||||
|
import { EditRegistryClient } from '../../ui/registry-clients/edit-registry-client/edit-registry-client.component';
|
||||||
|
import {
|
||||||
|
InlineServiceCreationRequest,
|
||||||
|
InlineServiceCreationResponse,
|
||||||
|
NewPropertyDialogRequest,
|
||||||
|
NewPropertyDialogResponse,
|
||||||
|
Property,
|
||||||
|
PropertyDescriptor
|
||||||
|
} from '../../../../state/shared';
|
||||||
|
import { NewPropertyDialog } from '../../../../ui/common/new-property-dialog/new-property-dialog.component';
|
||||||
|
import { ExtensionTypesService } from '../../../../service/extension-types.service';
|
||||||
|
import { CreateControllerService } from '../../../../ui/common/controller-service/create-controller-service/create-controller-service.component';
|
||||||
|
import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
|
||||||
|
import { Client } from '../../../../service/client.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RegistryClientsEffects {
|
||||||
|
constructor(
|
||||||
|
private actions$: Actions,
|
||||||
|
private store: Store<NiFiState>,
|
||||||
|
private client: Client,
|
||||||
|
private registryClientService: RegistryClientService,
|
||||||
|
private extensionTypesService: ExtensionTypesService,
|
||||||
|
private managementControllerServiceService: ManagementControllerServiceService,
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
loadRegistryClients$ = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.loadRegistryClients),
|
||||||
|
switchMap(() =>
|
||||||
|
from(this.registryClientService.getRegistryClients()).pipe(
|
||||||
|
map((response) =>
|
||||||
|
RegistryClientsActions.loadRegistryClientsSuccess({
|
||||||
|
response: {
|
||||||
|
registryClients: response.registries,
|
||||||
|
loadedTimestamp: response.currentTime
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
catchError((error) =>
|
||||||
|
of(
|
||||||
|
RegistryClientsActions.registryClientsApiError({
|
||||||
|
error: error.error
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
openNewRegistryClientDialog$ = createEffect(
|
||||||
|
() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.openNewRegistryClientDialog),
|
||||||
|
withLatestFrom(this.store.select(selectRegistryClientTypes)),
|
||||||
|
tap(([action, registryClientTypes]) => {
|
||||||
|
const dialogReference = this.dialog.open(CreateRegistryClient, {
|
||||||
|
data: {
|
||||||
|
registryClientTypes
|
||||||
|
},
|
||||||
|
panelClass: 'medium-dialog'
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogReference.componentInstance.saving$ = this.store.select(selectSaving);
|
||||||
|
|
||||||
|
dialogReference.componentInstance.createRegistryClient.pipe(take(1)).subscribe((request) => {
|
||||||
|
this.store.dispatch(
|
||||||
|
RegistryClientsActions.createRegistryClient({
|
||||||
|
request
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{ dispatch: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
createRegistryClient$ = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.createRegistryClient),
|
||||||
|
map((action) => action.request),
|
||||||
|
switchMap((request) =>
|
||||||
|
from(this.registryClientService.createRegistryClient(request)).pipe(
|
||||||
|
map((response) =>
|
||||||
|
RegistryClientsActions.createRegistryClientSuccess({
|
||||||
|
response: {
|
||||||
|
registryClient: response
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
catchError((error) =>
|
||||||
|
of(
|
||||||
|
RegistryClientsActions.registryClientsApiError({
|
||||||
|
error: error.error
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
createRegistryClientSuccess$ = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.createRegistryClientSuccess),
|
||||||
|
map((action) => action.response),
|
||||||
|
tap(() => {
|
||||||
|
this.dialog.closeAll();
|
||||||
|
}),
|
||||||
|
switchMap((response) =>
|
||||||
|
of(
|
||||||
|
RegistryClientsActions.selectClient({
|
||||||
|
request: {
|
||||||
|
id: response.registryClient.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
navigateToEditRegistryClient$ = createEffect(
|
||||||
|
() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.navigateToEditRegistryClient),
|
||||||
|
map((action) => action.id),
|
||||||
|
tap((id) => {
|
||||||
|
this.router.navigate(['/settings', 'registry-clients', id, 'edit']);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{ dispatch: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
openConfigureControllerServiceDialog$ = createEffect(
|
||||||
|
() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.openConfigureRegistryClientDialog),
|
||||||
|
map((action) => action.request),
|
||||||
|
tap((request) => {
|
||||||
|
const registryClientId: string = request.registryClient.id;
|
||||||
|
|
||||||
|
const editDialogReference = this.dialog.open(EditRegistryClient, {
|
||||||
|
data: request,
|
||||||
|
panelClass: 'large-dialog'
|
||||||
|
});
|
||||||
|
|
||||||
|
editDialogReference.componentInstance.saving$ = this.store.select(selectSaving);
|
||||||
|
|
||||||
|
editDialogReference.componentInstance.createNewProperty = (
|
||||||
|
existingProperties: string[],
|
||||||
|
allowsSensitive: boolean
|
||||||
|
): Observable<Property> => {
|
||||||
|
const dialogRequest: NewPropertyDialogRequest = { existingProperties, allowsSensitive };
|
||||||
|
const newPropertyDialogReference = this.dialog.open(NewPropertyDialog, {
|
||||||
|
data: dialogRequest,
|
||||||
|
panelClass: 'small-dialog'
|
||||||
|
});
|
||||||
|
|
||||||
|
return newPropertyDialogReference.componentInstance.newProperty.pipe(
|
||||||
|
take(1),
|
||||||
|
switchMap((dialogResponse: NewPropertyDialogResponse) => {
|
||||||
|
return this.registryClientService
|
||||||
|
.getPropertyDescriptor(
|
||||||
|
registryClientId,
|
||||||
|
dialogResponse.name,
|
||||||
|
dialogResponse.sensitive
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
map((response) => {
|
||||||
|
newPropertyDialogReference.close();
|
||||||
|
|
||||||
|
return {
|
||||||
|
property: dialogResponse.name,
|
||||||
|
value: null,
|
||||||
|
descriptor: response.propertyDescriptor
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
editDialogReference.componentInstance.getServiceLink = (serviceId: string) => {
|
||||||
|
return of(['/settings', 'management-controller-services', serviceId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
editDialogReference.componentInstance.createNewService = (
|
||||||
|
request: InlineServiceCreationRequest
|
||||||
|
): Observable<InlineServiceCreationResponse> => {
|
||||||
|
const descriptor: PropertyDescriptor = request.descriptor;
|
||||||
|
|
||||||
|
// fetch all services that implement the requested service api
|
||||||
|
return this.extensionTypesService
|
||||||
|
.getImplementingControllerServiceTypes(
|
||||||
|
// @ts-ignore
|
||||||
|
descriptor.identifiesControllerService,
|
||||||
|
descriptor.identifiesControllerServiceBundle
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
switchMap((implementingTypesResponse) => {
|
||||||
|
// show the create controller service dialog with the types that implemented the interface
|
||||||
|
const createServiceDialogReference = this.dialog.open(CreateControllerService, {
|
||||||
|
data: {
|
||||||
|
controllerServiceTypes: implementingTypesResponse.controllerServiceTypes
|
||||||
|
},
|
||||||
|
panelClass: 'medium-dialog'
|
||||||
|
});
|
||||||
|
|
||||||
|
return createServiceDialogReference.componentInstance.createControllerService.pipe(
|
||||||
|
take(1),
|
||||||
|
switchMap((controllerServiceType) => {
|
||||||
|
// typically this sequence would be implemented with ngrx actions, however we are
|
||||||
|
// currently in an edit session and we need to return both the value (new service id)
|
||||||
|
// and updated property descriptor so the table renders correctly
|
||||||
|
return this.managementControllerServiceService
|
||||||
|
.createControllerService({
|
||||||
|
revision: {
|
||||||
|
clientId: this.client.getClientId(),
|
||||||
|
version: 0
|
||||||
|
},
|
||||||
|
controllerServiceType: controllerServiceType.type,
|
||||||
|
controllerServiceBundle: controllerServiceType.bundle
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
switchMap((createReponse) => {
|
||||||
|
// fetch an updated property descriptor
|
||||||
|
return this.registryClientService
|
||||||
|
.getPropertyDescriptor(
|
||||||
|
registryClientId,
|
||||||
|
descriptor.name,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
map((descriptorResponse) => {
|
||||||
|
createServiceDialogReference.close();
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: createReponse.id,
|
||||||
|
descriptor:
|
||||||
|
descriptorResponse.propertyDescriptor
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
catchError((error) => {
|
||||||
|
// TODO - show error
|
||||||
|
return NEVER;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
editDialogReference.componentInstance.editRegistryClient
|
||||||
|
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||||
|
.subscribe((payload: any) => {
|
||||||
|
this.store.dispatch(
|
||||||
|
RegistryClientsActions.configureRegistryClient({
|
||||||
|
request: {
|
||||||
|
id: registryClientId,
|
||||||
|
uri: request.registryClient.uri,
|
||||||
|
payload
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
editDialogReference.afterClosed().subscribe((response) => {
|
||||||
|
if (response != 'ROUTED') {
|
||||||
|
this.store.dispatch(
|
||||||
|
RegistryClientsActions.selectClient({
|
||||||
|
request: {
|
||||||
|
id: registryClientId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{ dispatch: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
configureControllerService$ = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.configureRegistryClient),
|
||||||
|
map((action) => action.request),
|
||||||
|
switchMap((request) =>
|
||||||
|
from(this.registryClientService.updateRegistryClient(request)).pipe(
|
||||||
|
map((response) =>
|
||||||
|
RegistryClientsActions.configureRegistryClientSuccess({
|
||||||
|
response: {
|
||||||
|
id: request.id,
|
||||||
|
registryClient: response
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
catchError((error) =>
|
||||||
|
of(
|
||||||
|
RegistryClientsActions.registryClientsApiError({
|
||||||
|
error: error.error
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
configureRegistryClientSuccess = createEffect(
|
||||||
|
() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.configureRegistryClientSuccess),
|
||||||
|
tap(() => {
|
||||||
|
this.dialog.closeAll();
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{ dispatch: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
promptRegistryClientDeletion$ = createEffect(
|
||||||
|
() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.promptRegistryClientDeletion),
|
||||||
|
map((action) => action.request),
|
||||||
|
tap((request) => {
|
||||||
|
const dialogReference = this.dialog.open(YesNoDialog, {
|
||||||
|
data: {
|
||||||
|
title: 'Delete Registry Client',
|
||||||
|
message: `Delete registry client ${request.registryClient.component.name}?`
|
||||||
|
},
|
||||||
|
panelClass: 'small-dialog'
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => {
|
||||||
|
this.store.dispatch(
|
||||||
|
RegistryClientsActions.deleteRegistryClient({
|
||||||
|
request
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{ dispatch: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
deleteRegistryClient$ = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.deleteRegistryClient),
|
||||||
|
map((action) => action.request),
|
||||||
|
switchMap((request) =>
|
||||||
|
from(this.registryClientService.deleteRegistryClient(request)).pipe(
|
||||||
|
map((response) =>
|
||||||
|
RegistryClientsActions.deleteRegistryClientSuccess({
|
||||||
|
response: {
|
||||||
|
registryClient: response
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
catchError((error) =>
|
||||||
|
of(
|
||||||
|
RegistryClientsActions.registryClientsApiError({
|
||||||
|
error: error.error
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
selectRegistryClient$ = createEffect(
|
||||||
|
() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RegistryClientsActions.selectClient),
|
||||||
|
map((action) => action.request),
|
||||||
|
tap((request) => {
|
||||||
|
this.router.navigate(['/settings', 'registry-clients', request.id]);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{ dispatch: false }
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* 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 { createReducer, on } from '@ngrx/store';
|
||||||
|
import { produce } from 'immer';
|
||||||
|
import { RegistryClientsState } from './index';
|
||||||
|
import {
|
||||||
|
configureRegistryClient,
|
||||||
|
configureRegistryClientSuccess,
|
||||||
|
createRegistryClient,
|
||||||
|
createRegistryClientSuccess,
|
||||||
|
deleteRegistryClient,
|
||||||
|
deleteRegistryClientSuccess,
|
||||||
|
loadRegistryClients,
|
||||||
|
loadRegistryClientsSuccess,
|
||||||
|
registryClientsApiError
|
||||||
|
} from './registry-clients.actions';
|
||||||
|
|
||||||
|
export const initialState: RegistryClientsState = {
|
||||||
|
registryClients: [],
|
||||||
|
saving: false,
|
||||||
|
loadedTimestamp: '',
|
||||||
|
error: null,
|
||||||
|
status: 'pending'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const registryClientsReducer = createReducer(
|
||||||
|
initialState,
|
||||||
|
on(loadRegistryClients, (state) => ({
|
||||||
|
...state,
|
||||||
|
status: 'loading' as const
|
||||||
|
})),
|
||||||
|
on(loadRegistryClientsSuccess, (state, { response }) => ({
|
||||||
|
...state,
|
||||||
|
registryClients: response.registryClients,
|
||||||
|
loadedTimestamp: response.loadedTimestamp,
|
||||||
|
error: null,
|
||||||
|
status: 'success' as const
|
||||||
|
})),
|
||||||
|
on(registryClientsApiError, (state, { error }) => ({
|
||||||
|
...state,
|
||||||
|
saving: false,
|
||||||
|
error,
|
||||||
|
status: 'error' as const
|
||||||
|
})),
|
||||||
|
on(createRegistryClient, configureRegistryClient, deleteRegistryClient, (state, { request }) => ({
|
||||||
|
...state,
|
||||||
|
saving: true
|
||||||
|
})),
|
||||||
|
on(createRegistryClientSuccess, (state, { response }) => {
|
||||||
|
return produce(state, (draftState) => {
|
||||||
|
draftState.registryClients.push(response.registryClient);
|
||||||
|
draftState.saving = false;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
on(configureRegistryClientSuccess, (state, { response }) => {
|
||||||
|
return produce(state, (draftState) => {
|
||||||
|
const componentIndex: number = draftState.registryClients.findIndex((f: any) => response.id === f.id);
|
||||||
|
if (componentIndex > -1) {
|
||||||
|
draftState.registryClients[componentIndex] = response.registryClient;
|
||||||
|
}
|
||||||
|
draftState.saving = false;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
on(deleteRegistryClientSuccess, (state, { response }) => {
|
||||||
|
return produce(state, (draftState) => {
|
||||||
|
const componentIndex: number = draftState.registryClients.findIndex(
|
||||||
|
(f: any) => response.registryClient.id === f.id
|
||||||
|
);
|
||||||
|
if (componentIndex > -1) {
|
||||||
|
draftState.registryClients.splice(componentIndex, 1);
|
||||||
|
}
|
||||||
|
draftState.saving = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* 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 { createSelector } from '@ngrx/store';
|
||||||
|
import { selectSettingsState, SettingsState } from '../index';
|
||||||
|
import { selectCurrentRoute } from '../../../../state/router/router.selectors';
|
||||||
|
import { RegistryClientEntity, registryClientsFeatureKey, RegistryClientsState } from './index';
|
||||||
|
|
||||||
|
export const selectRegistryClientsState = createSelector(
|
||||||
|
selectSettingsState,
|
||||||
|
(state: SettingsState) => state[registryClientsFeatureKey]
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectSaving = createSelector(selectRegistryClientsState, (state: RegistryClientsState) => state.saving);
|
||||||
|
|
||||||
|
export const selectRegistryClientIdFromRoute = createSelector(selectCurrentRoute, (route) => {
|
||||||
|
if (route) {
|
||||||
|
// always select the registry client from the route
|
||||||
|
return route.params.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const selectSingleEditedRegistryClient = createSelector(selectCurrentRoute, (route) => {
|
||||||
|
if (route?.routeConfig?.path == 'edit') {
|
||||||
|
return route.params.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const selectRegistryClients = createSelector(
|
||||||
|
selectRegistryClientsState,
|
||||||
|
(state: RegistryClientsState) => state.registryClients
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectRegistryClient = (id: string) =>
|
||||||
|
createSelector(selectRegistryClients, (entities: RegistryClientEntity[]) =>
|
||||||
|
entities.find((entity) => id == entity.id)
|
||||||
|
);
|
|
@ -105,15 +105,23 @@ export class ReportingTasksEffects {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
createReportingTaskSuccess$ = createEffect(
|
createReportingTaskSuccess$ = createEffect(() =>
|
||||||
() =>
|
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(ReportingTaskActions.createReportingTaskSuccess),
|
ofType(ReportingTaskActions.createReportingTaskSuccess),
|
||||||
|
map((action) => action.response),
|
||||||
tap(() => {
|
tap(() => {
|
||||||
this.dialog.closeAll();
|
this.dialog.closeAll();
|
||||||
|
}),
|
||||||
|
switchMap((response) =>
|
||||||
|
of(
|
||||||
|
ReportingTaskActions.selectReportingTask({
|
||||||
|
request: {
|
||||||
|
reportingTask: response.reportingTask
|
||||||
|
}
|
||||||
})
|
})
|
||||||
),
|
)
|
||||||
{ dispatch: false }
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
promptReportingTaskDeletion$ = createEffect(
|
promptReportingTaskDeletion$ = createEffect(
|
||||||
|
@ -124,8 +132,8 @@ export class ReportingTasksEffects {
|
||||||
tap((request) => {
|
tap((request) => {
|
||||||
const dialogReference = this.dialog.open(YesNoDialog, {
|
const dialogReference = this.dialog.open(YesNoDialog, {
|
||||||
data: {
|
data: {
|
||||||
title: 'Delete Controller Service',
|
title: 'Delete Reporting Task',
|
||||||
message: `Delete controller service ${request.reportingTask.component.name}?`
|
message: `Delete reporting task ${request.reportingTask.component.name}?`
|
||||||
},
|
},
|
||||||
panelClass: 'small-dialog'
|
panelClass: 'small-dialog'
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { ReportingTasksState } from './index';
|
||||||
import {
|
import {
|
||||||
createReportingTask,
|
createReportingTask,
|
||||||
createReportingTaskSuccess,
|
createReportingTaskSuccess,
|
||||||
|
deleteReportingTask,
|
||||||
deleteReportingTaskSuccess,
|
deleteReportingTaskSuccess,
|
||||||
loadReportingTasks,
|
loadReportingTasks,
|
||||||
loadReportingTasksSuccess,
|
loadReportingTasksSuccess,
|
||||||
|
@ -56,7 +57,7 @@ export const reportingTasksReducer = createReducer(
|
||||||
error,
|
error,
|
||||||
status: 'error' as const
|
status: 'error' as const
|
||||||
})),
|
})),
|
||||||
on(createReportingTask, (state, { request }) => ({
|
on(createReportingTask, deleteReportingTask, (state, { request }) => ({
|
||||||
...state,
|
...state,
|
||||||
saving: true
|
saving: true
|
||||||
})),
|
})),
|
||||||
|
@ -94,6 +95,7 @@ export const reportingTasksReducer = createReducer(
|
||||||
if (componentIndex > -1) {
|
if (componentIndex > -1) {
|
||||||
draftState.reportingTasks.splice(componentIndex, 1);
|
draftState.reportingTasks.splice(componentIndex, 1);
|
||||||
}
|
}
|
||||||
|
draftState.saving = false;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,14 +16,18 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<div class="general-form w-96">
|
<div class="general-form w-96">
|
||||||
<form [formGroup]="controllerForm">
|
<form [formGroup]="controllerForm" *ngIf="currentUser$ | async; let currentUser">
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Maximum Timer Driven Thread Count</mat-label>
|
<mat-label>Maximum Timer Driven Thread Count</mat-label>
|
||||||
<input matInput formControlName="timerDrivenThreadCount" type="text" />
|
<input
|
||||||
|
matInput
|
||||||
|
formControlName="timerDrivenThreadCount"
|
||||||
|
[readonly]="!currentUser.controllerPermissions.canWrite"
|
||||||
|
type="text" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div *ngIf="currentUser.controllerPermissions.canWrite">
|
||||||
<button
|
<button
|
||||||
[disabled]="!controllerForm.dirty || controllerForm.invalid"
|
[disabled]="!controllerForm.dirty || controllerForm.invalid"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { ControllerEntity, GeneralState, UpdateControllerConfigRequest } from '.
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { updateControllerConfig } from '../../../state/general/general.actions';
|
import { updateControllerConfig } from '../../../state/general/general.actions';
|
||||||
import { Client } from '../../../../../service/client.service';
|
import { Client } from '../../../../../service/client.service';
|
||||||
|
import { selectUser } from '../../../../../state/user/user.selectors';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'general-form',
|
selector: 'general-form',
|
||||||
|
@ -35,6 +36,7 @@ export class GeneralForm {
|
||||||
this.controllerForm.get('timerDrivenThreadCount')?.setValue(controller.component.maxTimerDrivenThreadCount);
|
this.controllerForm.get('timerDrivenThreadCount')?.setValue(controller.component.maxTimerDrivenThreadCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentUser$ = this.store.select(selectUser);
|
||||||
controllerForm: FormGroup;
|
controllerForm: FormGroup;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
||||||
</div>
|
</div>
|
||||||
<ng-template #loaded>
|
<ng-template #loaded>
|
||||||
<div class="flex flex-col h-full gap-y-2">
|
<div class="flex flex-col h-full gap-y-2" *ngIf="currentUser$ | async; let currentUser">
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end" *ngIf="currentUser.controllerPermissions.canWrite">
|
||||||
<button class="nifi-button" (click)="openNewControllerServiceDialog()">
|
<button class="nifi-button" (click)="openNewControllerServiceDialog()">
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -36,6 +36,8 @@ import { ControllerServiceEntity } from '../../../../state/shared';
|
||||||
import { initialState } from '../../state/management-controller-services/management-controller-services.reducer';
|
import { initialState } from '../../state/management-controller-services/management-controller-services.reducer';
|
||||||
import { filter, switchMap, take } from 'rxjs';
|
import { filter, switchMap, take } from 'rxjs';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { selectUser } from '../../../../state/user/user.selectors';
|
||||||
|
import { NiFiState } from '../../../../state';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'management-controller-services',
|
selector: 'management-controller-services',
|
||||||
|
@ -45,8 +47,9 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
export class ManagementControllerServices implements OnInit {
|
export class ManagementControllerServices implements OnInit {
|
||||||
serviceState$ = this.store.select(selectManagementControllerServicesState);
|
serviceState$ = this.store.select(selectManagementControllerServicesState);
|
||||||
selectedServiceId$ = this.store.select(selectControllerServiceIdFromRoute);
|
selectedServiceId$ = this.store.select(selectControllerServiceIdFromRoute);
|
||||||
|
currentUser$ = this.store.select(selectUser);
|
||||||
|
|
||||||
constructor(private store: Store<ManagementControllerServicesState>) {
|
constructor(private store: Store<NiFiState>) {
|
||||||
this.store
|
this.store
|
||||||
.select(selectSingleEditedService)
|
.select(selectSingleEditedService)
|
||||||
.pipe(
|
.pipe(
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<h2 mat-dialog-title>Add Registry Client</h2>
|
||||||
|
<form class="create-registry-client-form" [formGroup]="createRegistryClientForm">
|
||||||
|
<mat-dialog-content>
|
||||||
|
<div>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Name</mat-label>
|
||||||
|
<input matInput formControlName="name" type="text" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Type</mat-label>
|
||||||
|
<mat-select formControlName="type">
|
||||||
|
<ng-container *ngFor="let option of request.registryClientTypes">
|
||||||
|
<ng-container *ngIf="option.description; else noDescription">
|
||||||
|
<mat-option
|
||||||
|
[value]="option.type"
|
||||||
|
nifiTooltip
|
||||||
|
[tooltipComponentType]="TextTip"
|
||||||
|
[tooltipInputData]="getOptionTipData(option)"
|
||||||
|
[delayClose]="false">
|
||||||
|
{{ formatType(option) }}
|
||||||
|
</mat-option>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #noDescription>
|
||||||
|
<mat-option [value]="option.type">
|
||||||
|
{{ formatType(option) }}
|
||||||
|
</mat-option>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Description</mat-label>
|
||||||
|
<textarea matInput formControlName="description" type="text"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions align="end" *ngIf="{ value: (saving$ | async)! } as saving">
|
||||||
|
<button color="accent" mat-raised-button mat-dialog-close>Cancel</button>
|
||||||
|
<button
|
||||||
|
[disabled]="!createRegistryClientForm.dirty || createRegistryClientForm.invalid || saving.value"
|
||||||
|
type="button"
|
||||||
|
color="primary"
|
||||||
|
(click)="createRegistryClientClicked()"
|
||||||
|
mat-raised-button>
|
||||||
|
<span *nifiSpinner="saving.value">Apply</span>
|
||||||
|
</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
</form>
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@use '@angular/material' as mat;
|
||||||
|
|
||||||
|
.create-registry-client-form {
|
||||||
|
@include mat.button-density(-1);
|
||||||
|
|
||||||
|
.mat-mdc-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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 { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { CreateRegistryClient } from './create-registry-client.component';
|
||||||
|
import { CreateRegistryClientDialogRequest } from '../../../state/registry-clients';
|
||||||
|
|
||||||
|
describe('CreateRegistryClient', () => {
|
||||||
|
let component: CreateRegistryClient;
|
||||||
|
let fixture: ComponentFixture<CreateRegistryClient>;
|
||||||
|
|
||||||
|
const data: CreateRegistryClientDialogRequest = {
|
||||||
|
registryClientTypes: [
|
||||||
|
{
|
||||||
|
type: 'org.apache.nifi.registry.flow.NifiRegistryFlowRegistryClient',
|
||||||
|
bundle: {
|
||||||
|
group: 'org.apache.nifi',
|
||||||
|
artifact: 'nifi-flow-registry-client-nar',
|
||||||
|
version: '2.0.0-SNAPSHOT'
|
||||||
|
},
|
||||||
|
restricted: false,
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CreateRegistryClient, BrowserAnimationsModule],
|
||||||
|
providers: [{ provide: MAT_DIALOG_DATA, useValue: data }]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(CreateRegistryClient);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* 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, EventEmitter, Inject, Input, Output } from '@angular/core';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { DocumentedType, TextTipInput } from '../../../../../state/shared';
|
||||||
|
import { CreateRegistryClientDialogRequest, CreateRegistryClientRequest } from '../../../state/registry-clients';
|
||||||
|
import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
|
||||||
|
import { Client } from '../../../../../service/client.service';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||||
|
import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component';
|
||||||
|
import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'create-registry-client',
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: './create-registry-client.component.html',
|
||||||
|
imports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatDialogModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
MatButtonModule,
|
||||||
|
NgIf,
|
||||||
|
AsyncPipe,
|
||||||
|
NifiSpinnerDirective,
|
||||||
|
MatSelectModule,
|
||||||
|
NgForOf,
|
||||||
|
NifiTooltipDirective
|
||||||
|
],
|
||||||
|
styleUrls: ['./create-registry-client.component.scss']
|
||||||
|
})
|
||||||
|
export class CreateRegistryClient {
|
||||||
|
@Input() saving$!: Observable<boolean>;
|
||||||
|
@Output() createRegistryClient: EventEmitter<CreateRegistryClientRequest> =
|
||||||
|
new EventEmitter<CreateRegistryClientRequest>();
|
||||||
|
|
||||||
|
protected readonly TextTip = TextTip;
|
||||||
|
|
||||||
|
createRegistryClientForm: FormGroup;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(MAT_DIALOG_DATA) public request: CreateRegistryClientDialogRequest,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private nifiCommon: NiFiCommon,
|
||||||
|
private client: Client
|
||||||
|
) {
|
||||||
|
let type: string | null = null;
|
||||||
|
if (request.registryClientTypes.length > 0) {
|
||||||
|
type = request.registryClientTypes[0].type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the form
|
||||||
|
this.createRegistryClientForm = this.formBuilder.group({
|
||||||
|
name: new FormControl('', Validators.required),
|
||||||
|
type: new FormControl(type, Validators.required),
|
||||||
|
description: new FormControl('')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
formatType(option: DocumentedType): string {
|
||||||
|
return this.nifiCommon.substringAfterLast(option.type, '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptionTipData(option: DocumentedType): TextTipInput {
|
||||||
|
return {
|
||||||
|
// @ts-ignore
|
||||||
|
text: option.description
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createRegistryClientClicked() {
|
||||||
|
const request: CreateRegistryClientRequest = {
|
||||||
|
revision: {
|
||||||
|
clientId: this.client.getClientId(),
|
||||||
|
version: 0
|
||||||
|
},
|
||||||
|
component: {
|
||||||
|
name: this.createRegistryClientForm.get('name')?.value,
|
||||||
|
type: this.createRegistryClientForm.get('type')?.value,
|
||||||
|
description: this.createRegistryClientForm.get('description')?.value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.createRegistryClient.next(request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<h2 mat-dialog-title>Edit Registry Client</h2>
|
||||||
|
<form class="edit-registry-client-form" [formGroup]="editRegistryClientForm">
|
||||||
|
<mat-dialog-content>
|
||||||
|
<mat-tab-group>
|
||||||
|
<mat-tab label="Settings">
|
||||||
|
<div class="tab-content py-4 flex flex-col">
|
||||||
|
<div class="flex flex-col mb-5">
|
||||||
|
<div>Id</div>
|
||||||
|
<div class="value">{{ request.registryClient.id }}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Name</mat-label>
|
||||||
|
<input matInput formControlName="name" type="text" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mb-5">
|
||||||
|
<div>Id</div>
|
||||||
|
<div class="value">{{ request.registryClient.component.type }}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Description</mat-label>
|
||||||
|
<textarea matInput formControlName="description" type="text"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="Properties">
|
||||||
|
<div class="tab-content py-4">
|
||||||
|
<property-table
|
||||||
|
formControlName="properties"
|
||||||
|
[createNewProperty]="createNewProperty"
|
||||||
|
[createNewService]="createNewService"
|
||||||
|
[getParameters]="getParameters"
|
||||||
|
[getServiceLink]="getServiceLink"
|
||||||
|
[supportsSensitiveDynamicProperties]="
|
||||||
|
request.registryClient.component.supportsSensitiveDynamicProperties
|
||||||
|
"></property-table>
|
||||||
|
</div>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions align="end" *ngIf="{ value: (saving$ | async)! } as saving">
|
||||||
|
<button color="accent" mat-raised-button mat-dialog-close>Cancel</button>
|
||||||
|
<button
|
||||||
|
[disabled]="!editRegistryClientForm.dirty || editRegistryClientForm.invalid || saving.value"
|
||||||
|
type="button"
|
||||||
|
color="primary"
|
||||||
|
(click)="createRegistryClientClicked()"
|
||||||
|
mat-raised-button>
|
||||||
|
<span *nifiSpinner="saving.value">Apply</span>
|
||||||
|
</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
</form>
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@use '@angular/material' as mat;
|
||||||
|
|
||||||
|
.edit-registry-client-form {
|
||||||
|
@include mat.button-density(-1);
|
||||||
|
|
||||||
|
.mdc-dialog__content {
|
||||||
|
padding: 0 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
height: 475px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* 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 { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { EditRegistryClient } from './edit-registry-client.component';
|
||||||
|
import { EditRegistryClientDialogRequest } from '../../../state/registry-clients';
|
||||||
|
|
||||||
|
describe('EditRegistryClient', () => {
|
||||||
|
let component: EditRegistryClient;
|
||||||
|
let fixture: ComponentFixture<EditRegistryClient>;
|
||||||
|
|
||||||
|
const data: EditRegistryClientDialogRequest = {
|
||||||
|
registryClient: {
|
||||||
|
revision: {
|
||||||
|
clientId: 'fdbbc975-5fd5-4dbf-9308-432d75d20c04',
|
||||||
|
version: 2
|
||||||
|
},
|
||||||
|
id: '454cab42-018c-1000-6f9f-3603643c504c',
|
||||||
|
uri: 'https://localhost:4200/nifi-api/controller/registry-clients/454cab42-018c-1000-6f9f-3603643c504c',
|
||||||
|
permissions: {
|
||||||
|
canRead: true,
|
||||||
|
canWrite: true
|
||||||
|
},
|
||||||
|
component: {
|
||||||
|
id: '454cab42-018c-1000-6f9f-3603643c504c',
|
||||||
|
name: 'registry',
|
||||||
|
description: '',
|
||||||
|
type: 'org.apache.nifi.registry.flow.NifiRegistryFlowRegistryClient',
|
||||||
|
bundle: {
|
||||||
|
group: 'org.apache.nifi',
|
||||||
|
artifact: 'nifi-flow-registry-client-nar',
|
||||||
|
version: '2.0.0-SNAPSHOT'
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
url: null,
|
||||||
|
'ssl-context-service': null
|
||||||
|
},
|
||||||
|
descriptors: {
|
||||||
|
url: {
|
||||||
|
name: 'url',
|
||||||
|
displayName: 'URL',
|
||||||
|
description: 'URL of the NiFi Registry',
|
||||||
|
required: true,
|
||||||
|
sensitive: false,
|
||||||
|
dynamic: false,
|
||||||
|
supportsEl: false,
|
||||||
|
expressionLanguageScope: 'Not Supported',
|
||||||
|
dependencies: []
|
||||||
|
},
|
||||||
|
'ssl-context-service': {
|
||||||
|
name: 'ssl-context-service',
|
||||||
|
displayName: 'SSL Context Service',
|
||||||
|
description: 'Specifies the SSL Context Service to use for communicating with NiFiRegistry',
|
||||||
|
allowableValues: [
|
||||||
|
{
|
||||||
|
allowableValue: {
|
||||||
|
displayName: 'StandardRestrictedSSLContextService',
|
||||||
|
value: '45b3d2bf-018c-1000-f99a-fd441220c9d7'
|
||||||
|
},
|
||||||
|
canRead: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
required: false,
|
||||||
|
sensitive: false,
|
||||||
|
dynamic: false,
|
||||||
|
supportsEl: false,
|
||||||
|
expressionLanguageScope: 'Not Supported',
|
||||||
|
identifiesControllerService: 'org.apache.nifi.ssl.SSLContextService',
|
||||||
|
identifiesControllerServiceBundle: {
|
||||||
|
group: 'org.apache.nifi',
|
||||||
|
artifact: 'nifi-standard-services-api-nar',
|
||||||
|
version: '2.0.0-SNAPSHOT'
|
||||||
|
},
|
||||||
|
dependencies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportsSensitiveDynamicProperties: false,
|
||||||
|
restricted: false,
|
||||||
|
deprecated: false,
|
||||||
|
validationErrors: ["'URL' is invalid because URL is required"],
|
||||||
|
validationStatus: 'INVALID',
|
||||||
|
multipleVersionsAvailable: false,
|
||||||
|
extensionMissing: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [EditRegistryClient, BrowserAnimationsModule],
|
||||||
|
providers: [{ provide: MAT_DIALOG_DATA, useValue: data }]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(EditRegistryClient);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* 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, EventEmitter, Inject, Input, Output } from '@angular/core';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { AbstractControl, FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import {
|
||||||
|
DocumentedType,
|
||||||
|
InlineServiceCreationRequest,
|
||||||
|
InlineServiceCreationResponse,
|
||||||
|
Parameter,
|
||||||
|
Property,
|
||||||
|
TextTipInput
|
||||||
|
} from '../../../../../state/shared';
|
||||||
|
import { EditRegistryClientDialogRequest } from '../../../state/registry-clients';
|
||||||
|
import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
|
||||||
|
import { Client } from '../../../../../service/client.service';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||||
|
import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component';
|
||||||
|
import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
||||||
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
|
import { PropertyTable } from '../../../../../ui/common/property-table/property-table.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'edit-registry-client',
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: './edit-registry-client.component.html',
|
||||||
|
imports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatDialogModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
MatButtonModule,
|
||||||
|
NgIf,
|
||||||
|
AsyncPipe,
|
||||||
|
NifiSpinnerDirective,
|
||||||
|
MatSelectModule,
|
||||||
|
NgForOf,
|
||||||
|
NifiTooltipDirective,
|
||||||
|
MatTabsModule,
|
||||||
|
PropertyTable
|
||||||
|
],
|
||||||
|
styleUrls: ['./edit-registry-client.component.scss']
|
||||||
|
})
|
||||||
|
export class EditRegistryClient {
|
||||||
|
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
|
||||||
|
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
|
||||||
|
@Input() getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
|
||||||
|
@Input() getServiceLink!: (serviceId: string) => Observable<string[]>;
|
||||||
|
@Input() saving$!: Observable<boolean>;
|
||||||
|
@Output() editRegistryClient: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
|
protected readonly TextTip = TextTip;
|
||||||
|
|
||||||
|
editRegistryClientForm: FormGroup;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(MAT_DIALOG_DATA) public request: EditRegistryClientDialogRequest,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private nifiCommon: NiFiCommon,
|
||||||
|
private client: Client
|
||||||
|
) {
|
||||||
|
const serviceProperties: any = request.registryClient.component.properties;
|
||||||
|
const properties: Property[] = Object.entries(serviceProperties).map((entry: any) => {
|
||||||
|
const [property, value] = entry;
|
||||||
|
return {
|
||||||
|
property,
|
||||||
|
value,
|
||||||
|
descriptor: request.registryClient.component.descriptors[property]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// build the form
|
||||||
|
this.editRegistryClientForm = this.formBuilder.group({
|
||||||
|
name: new FormControl(request.registryClient.component.name, Validators.required),
|
||||||
|
description: new FormControl(request.registryClient.component.description),
|
||||||
|
properties: new FormControl(properties)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
formatType(option: DocumentedType): string {
|
||||||
|
return this.nifiCommon.substringAfterLast(option.type, '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptionTipData(option: DocumentedType): TextTipInput {
|
||||||
|
return {
|
||||||
|
// @ts-ignore
|
||||||
|
text: option.description
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createRegistryClientClicked() {
|
||||||
|
const payload: any = {
|
||||||
|
revision: this.client.getRevision(this.request.registryClient),
|
||||||
|
component: {
|
||||||
|
id: this.request.registryClient.id,
|
||||||
|
name: this.editRegistryClientForm.get('name')?.value,
|
||||||
|
type: this.editRegistryClientForm.get('type')?.value,
|
||||||
|
description: this.editRegistryClientForm.get('description')?.value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const propertyControl: AbstractControl | null = this.editRegistryClientForm.get('properties');
|
||||||
|
if (propertyControl && propertyControl.dirty) {
|
||||||
|
const properties: Property[] = propertyControl.value;
|
||||||
|
const values: { [key: string]: string | null } = {};
|
||||||
|
properties.forEach((property) => (values[property.property] = property.value));
|
||||||
|
payload.component.properties = values;
|
||||||
|
payload.component.sensitiveDynamicPropertyNames = properties
|
||||||
|
.filter((property) => property.descriptor.dynamic && property.descriptor.sensitive)
|
||||||
|
.map((property) => property.descriptor.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editRegistryClient.next(payload);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
<!--
|
||||||
|
~ 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="relative h-full border">
|
||||||
|
<div class="registry-client-table listing-table absolute inset-0 overflow-y-auto">
|
||||||
|
<table
|
||||||
|
mat-table
|
||||||
|
[dataSource]="dataSource"
|
||||||
|
matSort
|
||||||
|
matSortDisableClear
|
||||||
|
(matSortChange)="updateSort($event)"
|
||||||
|
[matSortActive]="sort.active"
|
||||||
|
[matSortDirection]="sort.direction">
|
||||||
|
<!-- More Details Column -->
|
||||||
|
<ng-container matColumnDef="moreDetails">
|
||||||
|
<th mat-header-cell *matHeaderCellDef></th>
|
||||||
|
<td mat-cell *matCellDef="let item">
|
||||||
|
<ng-container *ngIf="canRead(item)">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<!-- TODO - handle read only in configure component? -->
|
||||||
|
<div
|
||||||
|
class="mr-3 pointer fa fa-warning has-errors"
|
||||||
|
*ngIf="hasErrors(item)"
|
||||||
|
nifiTooltip
|
||||||
|
[tooltipComponentType]="ValidationErrorsTip"
|
||||||
|
[tooltipInputData]="getValidationErrorsTipData(item)"></div>
|
||||||
|
<div
|
||||||
|
class="mr-3 pointer fa fa-sticky-note-o"
|
||||||
|
*ngIf="hasBulletins(item)"
|
||||||
|
nifiTooltip
|
||||||
|
[tooltipComponentType]="BulletinsTip"
|
||||||
|
[tooltipInputData]="getBulletinsTipData(item)"></div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Name Column -->
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
|
||||||
|
<td mat-cell *matCellDef="let item">
|
||||||
|
<ng-container *ngIf="canRead(item); else nameNoPermissions">
|
||||||
|
{{ item.component.name }}
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #nameNoPermissions>
|
||||||
|
<div class="unset">{{ item.id }}</div>
|
||||||
|
</ng-template>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Description Column -->
|
||||||
|
<ng-container matColumnDef="description">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Description</th>
|
||||||
|
<td mat-cell *matCellDef="let item">
|
||||||
|
<ng-container *ngIf="canRead(item); else descriptionNoPermissions">
|
||||||
|
{{ item.component.description }}
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #descriptionNoPermissions>
|
||||||
|
<div class="unset">{{ item.id }}</div>
|
||||||
|
</ng-template>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Type Column -->
|
||||||
|
<ng-container matColumnDef="type">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>
|
||||||
|
<td mat-cell *matCellDef="let item">
|
||||||
|
<ng-container *ngIf="canRead(item)">
|
||||||
|
{{ formatType(item) }}
|
||||||
|
</ng-container>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Bundle Column -->
|
||||||
|
<ng-container matColumnDef="bundle">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Bundle</th>
|
||||||
|
<td mat-cell *matCellDef="let item">
|
||||||
|
<ng-container *ngIf="canRead(item)">
|
||||||
|
{{ formatBundle(item) }}
|
||||||
|
</ng-container>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Actions Column -->
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef></th>
|
||||||
|
<td mat-cell *matCellDef="let item">
|
||||||
|
<div class="flex items-center gap-x-3">
|
||||||
|
<div
|
||||||
|
class="pointer fa fa-pencil"
|
||||||
|
*ngIf="canConfigure(item)"
|
||||||
|
(click)="configureClicked(item, $event)"
|
||||||
|
title="Configure"></div>
|
||||||
|
<div
|
||||||
|
class="pointer fa fa-trash"
|
||||||
|
*ngIf="canDelete(item)"
|
||||||
|
(click)="deleteClicked(item, $event)"
|
||||||
|
title="Delete"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||||
|
<tr
|
||||||
|
mat-row
|
||||||
|
*matRowDef="let row; let even = even; columns: displayedColumns"
|
||||||
|
(click)="select(row)"
|
||||||
|
[class.selected]="isSelected(row)"
|
||||||
|
[class.even]="even"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.registry-client-table.listing-table {
|
||||||
|
table {
|
||||||
|
.mat-column-moreDetails {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { RegistryClientTable } from './registry-client-table.component';
|
||||||
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
describe('RegistryClientTable', () => {
|
||||||
|
let component: RegistryClientTable;
|
||||||
|
let fixture: ComponentFixture<RegistryClientTable>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [RegistryClientTable],
|
||||||
|
imports: [MatTableModule, MatSortModule, BrowserAnimationsModule]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(RegistryClientTable);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* 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, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
|
import { Sort } from '@angular/material/sort';
|
||||||
|
import { ReportingTaskEntity } from '../../../state/reporting-tasks';
|
||||||
|
import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component';
|
||||||
|
import { BulletinsTip } from '../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
|
||||||
|
import { ValidationErrorsTip } from '../../../../../ui/common/tooltips/validation-errors-tip/validation-errors-tip.component';
|
||||||
|
import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
||||||
|
import { BulletinsTipInput, ValidationErrorsTipInput } from '../../../../../state/shared';
|
||||||
|
import { RegistryClientEntity } from '../../../state/registry-clients';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'registry-client-table',
|
||||||
|
templateUrl: './registry-client-table.component.html',
|
||||||
|
styleUrls: ['./registry-client-table.component.scss', '../../../../../../assets/styles/listing-table.scss']
|
||||||
|
})
|
||||||
|
export class RegistryClientTable {
|
||||||
|
@Input() set registryClients(registryClientEntities: RegistryClientEntity[]) {
|
||||||
|
if (registryClientEntities) {
|
||||||
|
this.dataSource.data = this.sortEvents(registryClientEntities, this.sort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input() selectedRegistryClientId!: string;
|
||||||
|
|
||||||
|
@Output() selectRegistryClient: EventEmitter<RegistryClientEntity> = new EventEmitter<RegistryClientEntity>();
|
||||||
|
@Output() configureRegistryClient: EventEmitter<RegistryClientEntity> = new EventEmitter<RegistryClientEntity>();
|
||||||
|
@Output() deleteRegistryClient: EventEmitter<RegistryClientEntity> = new EventEmitter<RegistryClientEntity>();
|
||||||
|
|
||||||
|
protected readonly TextTip = TextTip;
|
||||||
|
protected readonly BulletinsTip = BulletinsTip;
|
||||||
|
protected readonly ValidationErrorsTip = ValidationErrorsTip;
|
||||||
|
|
||||||
|
displayedColumns: string[] = ['moreDetails', 'name', 'description', 'type', 'bundle', 'actions'];
|
||||||
|
dataSource: MatTableDataSource<RegistryClientEntity> = new MatTableDataSource<RegistryClientEntity>();
|
||||||
|
|
||||||
|
sort: Sort = {
|
||||||
|
active: 'name',
|
||||||
|
direction: 'asc'
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(private nifiCommon: NiFiCommon) {}
|
||||||
|
|
||||||
|
canRead(entity: RegistryClientEntity): boolean {
|
||||||
|
return entity.permissions.canRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
canWrite(entity: RegistryClientEntity): boolean {
|
||||||
|
return entity.permissions.canWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasErrors(entity: RegistryClientEntity): boolean {
|
||||||
|
return this.canRead(entity) && !this.nifiCommon.isEmpty(entity.component.validationErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
getValidationErrorsTipData(entity: RegistryClientEntity): ValidationErrorsTipInput {
|
||||||
|
return {
|
||||||
|
isValidating: false,
|
||||||
|
validationErrors: entity.component.validationErrors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
hasBulletins(entity: RegistryClientEntity): boolean {
|
||||||
|
return this.canRead(entity) && !this.nifiCommon.isEmpty(entity.bulletins);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBulletinsTipData(entity: RegistryClientEntity): BulletinsTipInput {
|
||||||
|
return {
|
||||||
|
// @ts-ignore
|
||||||
|
bulletins: entity.bulletins
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
formatType(entity: RegistryClientEntity): string {
|
||||||
|
return this.nifiCommon.formatType(entity.component);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatBundle(entity: RegistryClientEntity): string {
|
||||||
|
return this.nifiCommon.formatBundle(entity.component.bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSort(sort: Sort): void {
|
||||||
|
this.sort = sort;
|
||||||
|
this.dataSource.data = this.sortEvents(this.dataSource.data, sort);
|
||||||
|
}
|
||||||
|
|
||||||
|
sortEvents(entities: RegistryClientEntity[], sort: Sort): RegistryClientEntity[] {
|
||||||
|
const data: RegistryClientEntity[] = entities.slice();
|
||||||
|
return data.sort((a, b) => {
|
||||||
|
const isAsc = sort.direction === 'asc';
|
||||||
|
|
||||||
|
let retVal: number = 0;
|
||||||
|
switch (sort.active) {
|
||||||
|
case 'name':
|
||||||
|
retVal = this.nifiCommon.compareString(a.component.name, b.component.name);
|
||||||
|
break;
|
||||||
|
case 'description':
|
||||||
|
retVal = this.nifiCommon.compareString(a.component.description, b.component.description);
|
||||||
|
break;
|
||||||
|
case 'type':
|
||||||
|
retVal = this.nifiCommon.compareString(this.formatType(a), this.formatType(b));
|
||||||
|
break;
|
||||||
|
case 'bundle':
|
||||||
|
retVal = this.nifiCommon.compareString(this.formatBundle(a), this.formatBundle(b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal * (isAsc ? 1 : -1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
canConfigure(entity: RegistryClientEntity): boolean {
|
||||||
|
return this.canRead(entity) && this.canWrite(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
configureClicked(entity: RegistryClientEntity, event: MouseEvent): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.configureRegistryClient.next(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
canDelete(entity: RegistryClientEntity): boolean {
|
||||||
|
return this.canRead(entity) && this.canWrite(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteClicked(entity: RegistryClientEntity, event: MouseEvent): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.deleteRegistryClient.next(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
select(entity: ReportingTaskEntity): void {
|
||||||
|
this.selectRegistryClient.next(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelected(entity: ReportingTaskEntity): boolean {
|
||||||
|
if (this.selectedRegistryClientId) {
|
||||||
|
return entity.id == this.selectedRegistryClientId;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,4 +15,34 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<p>registry-clients works!</p>
|
<ng-container *ngIf="registryClientsState$ | async; let registryClientState">
|
||||||
|
<div *ngIf="isInitialLoading(registryClientState); else loaded">
|
||||||
|
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
||||||
|
</div>
|
||||||
|
<ng-template #loaded>
|
||||||
|
<div class="flex flex-col h-full gap-y-2" *ngIf="currentUser$ | async; let currentUser">
|
||||||
|
<div class="flex justify-end" *ngIf="currentUser.controllerPermissions.canWrite">
|
||||||
|
<button class="nifi-button" (click)="openNewRegistryClientDialog()">
|
||||||
|
<i class="fa fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<registry-client-table
|
||||||
|
[selectedRegistryClientId]="selectedRegistryClientId$ | async"
|
||||||
|
[registryClients]="registryClientState.registryClients"
|
||||||
|
(selectRegistryClient)="selectRegistryClient($event)"
|
||||||
|
(configureRegistryClient)="configureRegistryClient($event)"
|
||||||
|
(deleteRegistryClient)="deleteRegistryClient($event)"></registry-client-table>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div class="refresh-container flex items-center gap-x-2">
|
||||||
|
<button class="nifi-button" (click)="refreshRegistryClientListing()">
|
||||||
|
<i class="fa fa-refresh" [class.fa-spin]="registryClientState.status === 'loading'"></i>
|
||||||
|
</button>
|
||||||
|
<div>Last updated:</div>
|
||||||
|
<div class="refresh-timestamp">{{ registryClientState.loadedTimestamp }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { RegistryClients } from './registry-clients.component';
|
import { RegistryClients } from './registry-clients.component';
|
||||||
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { initialState } from '../../state/registry-clients/registry-clients.reducer';
|
||||||
|
|
||||||
describe('RegistryClients', () => {
|
describe('RegistryClients', () => {
|
||||||
let component: RegistryClients;
|
let component: RegistryClients;
|
||||||
|
@ -25,7 +27,8 @@ describe('RegistryClients', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [RegistryClients]
|
declarations: [RegistryClients],
|
||||||
|
providers: [provideMockStore({ initialState })]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(RegistryClients);
|
fixture = TestBed.createComponent(RegistryClients);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|
|
@ -15,11 +15,107 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import {
|
||||||
|
selectRegistryClient,
|
||||||
|
selectRegistryClientIdFromRoute,
|
||||||
|
selectRegistryClientsState,
|
||||||
|
selectSingleEditedRegistryClient
|
||||||
|
} from '../../state/registry-clients/registry-clients.selectors';
|
||||||
|
import {
|
||||||
|
loadRegistryClients,
|
||||||
|
navigateToEditRegistryClient,
|
||||||
|
openConfigureRegistryClientDialog,
|
||||||
|
openNewRegistryClientDialog,
|
||||||
|
promptRegistryClientDeletion,
|
||||||
|
selectClient
|
||||||
|
} from '../../state/registry-clients/registry-clients.actions';
|
||||||
|
import { RegistryClientEntity, RegistryClientsState } from '../../state/registry-clients';
|
||||||
|
import { initialState } from '../../state/registry-clients/registry-clients.reducer';
|
||||||
|
import { selectUser } from '../../../../state/user/user.selectors';
|
||||||
|
import { NiFiState } from '../../../../state';
|
||||||
|
import { filter, switchMap, take } from 'rxjs';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'registry-clients',
|
selector: 'registry-clients',
|
||||||
templateUrl: './registry-clients.component.html',
|
templateUrl: './registry-clients.component.html',
|
||||||
styleUrls: ['./registry-clients.component.scss']
|
styleUrls: ['./registry-clients.component.scss']
|
||||||
})
|
})
|
||||||
export class RegistryClients {}
|
export class RegistryClients implements OnInit {
|
||||||
|
registryClientsState$ = this.store.select(selectRegistryClientsState);
|
||||||
|
selectedRegistryClientId$ = this.store.select(selectRegistryClientIdFromRoute);
|
||||||
|
currentUser$ = this.store.select(selectUser);
|
||||||
|
|
||||||
|
constructor(private store: Store<NiFiState>) {
|
||||||
|
this.store
|
||||||
|
.select(selectSingleEditedRegistryClient)
|
||||||
|
.pipe(
|
||||||
|
filter((id: string) => id != null),
|
||||||
|
switchMap((id: string) =>
|
||||||
|
this.store.select(selectRegistryClient(id)).pipe(
|
||||||
|
filter((entity) => entity != null),
|
||||||
|
take(1)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
takeUntilDestroyed()
|
||||||
|
)
|
||||||
|
.subscribe((entity) => {
|
||||||
|
if (entity) {
|
||||||
|
this.store.dispatch(
|
||||||
|
openConfigureRegistryClientDialog({
|
||||||
|
request: {
|
||||||
|
registryClient: entity
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.store.dispatch(loadRegistryClients());
|
||||||
|
}
|
||||||
|
|
||||||
|
isInitialLoading(state: RegistryClientsState): boolean {
|
||||||
|
// using the current timestamp to detect the initial load event
|
||||||
|
return state.loadedTimestamp == initialState.loadedTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
openNewRegistryClientDialog(): void {
|
||||||
|
this.store.dispatch(openNewRegistryClientDialog());
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshRegistryClientListing(): void {
|
||||||
|
this.store.dispatch(loadRegistryClients());
|
||||||
|
}
|
||||||
|
|
||||||
|
selectRegistryClient(entity: RegistryClientEntity): void {
|
||||||
|
this.store.dispatch(
|
||||||
|
selectClient({
|
||||||
|
request: {
|
||||||
|
id: entity.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
configureRegistryClient(entity: RegistryClientEntity): void {
|
||||||
|
this.store.dispatch(
|
||||||
|
navigateToEditRegistryClient({
|
||||||
|
id: entity.id
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRegistryClient(entity: RegistryClientEntity): void {
|
||||||
|
this.store.dispatch(
|
||||||
|
promptRegistryClientDeletion({
|
||||||
|
request: {
|
||||||
|
registryClient: entity
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,10 +18,15 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RegistryClients } from './registry-clients.component';
|
import { RegistryClients } from './registry-clients.component';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
import { RegistryClientTable } from './registry-client-table/registry-client-table.component';
|
||||||
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
|
import { NifiTooltipDirective } from '../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [RegistryClients],
|
declarations: [RegistryClients, RegistryClientTable],
|
||||||
exports: [RegistryClients],
|
exports: [RegistryClients],
|
||||||
imports: [CommonModule]
|
imports: [CommonModule, NgxSkeletonLoaderModule, MatTableModule, MatSortModule, NifiTooltipDirective]
|
||||||
})
|
})
|
||||||
export class RegistryClientsModule {}
|
export class RegistryClientsModule {}
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
||||||
</div>
|
</div>
|
||||||
<ng-template #loaded>
|
<ng-template #loaded>
|
||||||
<div class="flex flex-col h-full gap-y-2">
|
<div class="flex flex-col h-full gap-y-2" *ngIf="currentUser$ | async; let currentUser">
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end" *ngIf="currentUser.controllerPermissions.canWrite">
|
||||||
<button class="nifi-button" (click)="openNewReportingTaskDialog()">
|
<button class="nifi-button" (click)="openNewReportingTaskDialog()">
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -31,6 +31,8 @@ import {
|
||||||
stopReportingTask
|
stopReportingTask
|
||||||
} from '../../state/reporting-tasks/reporting-tasks.actions';
|
} from '../../state/reporting-tasks/reporting-tasks.actions';
|
||||||
import { initialState } from '../../state/reporting-tasks/reporting-tasks.reducer';
|
import { initialState } from '../../state/reporting-tasks/reporting-tasks.reducer';
|
||||||
|
import { selectUser } from '../../../../state/user/user.selectors';
|
||||||
|
import { NiFiState } from '../../../../state';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'reporting-tasks',
|
selector: 'reporting-tasks',
|
||||||
|
@ -40,8 +42,9 @@ import { initialState } from '../../state/reporting-tasks/reporting-tasks.reduce
|
||||||
export class ReportingTasks implements OnInit {
|
export class ReportingTasks implements OnInit {
|
||||||
reportingTaskState$ = this.store.select(selectReportingTasksState);
|
reportingTaskState$ = this.store.select(selectReportingTasksState);
|
||||||
selectedReportingTaskId$ = this.store.select(selectReportingTaskIdFromRoute);
|
selectedReportingTaskId$ = this.store.select(selectReportingTaskIdFromRoute);
|
||||||
|
currentUser$ = this.store.select(selectUser);
|
||||||
|
|
||||||
constructor(private store: Store<ReportingTasksState>) {}
|
constructor(private store: Store<NiFiState>) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.store.dispatch(loadReportingTasks());
|
this.store.dispatch(loadReportingTasks());
|
||||||
|
|
|
@ -48,6 +48,10 @@ export class ExtensionTypesService {
|
||||||
return this.httpClient.get(`${ExtensionTypesService.API}/flow/reporting-task-types`);
|
return this.httpClient.get(`${ExtensionTypesService.API}/flow/reporting-task-types`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRegistryClientTypes(): Observable<any> {
|
||||||
|
return this.httpClient.get(`${ExtensionTypesService.API}/controller/registry-types`);
|
||||||
|
}
|
||||||
|
|
||||||
getPrioritizers(): Observable<any> {
|
getPrioritizers(): Observable<any> {
|
||||||
return this.httpClient.get(`${ExtensionTypesService.API}/flow/prioritizers`);
|
return this.httpClient.get(`${ExtensionTypesService.API}/flow/prioritizers`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,11 @@
|
||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { CanMatchFn } from '@angular/router';
|
import { CanMatchFn } from '@angular/router';
|
||||||
|
|
||||||
import { authGuard } from './auth.guard';
|
import { authenticationGuard } from './authentication.guard';
|
||||||
|
|
||||||
describe('authGuard', () => {
|
describe('authenticationGuard', () => {
|
||||||
const executeGuard: CanMatchFn = (...guardParameters) =>
|
const executeGuard: CanMatchFn = (...guardParameters) =>
|
||||||
TestBed.runInInjectionContext(() => authGuard(...guardParameters));
|
TestBed.runInInjectionContext(() => authenticationGuard(...guardParameters));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({});
|
TestBed.configureTestingModule({});
|
|
@ -26,7 +26,7 @@ import { UserState } from '../../state/user';
|
||||||
import { loadUserSuccess } from '../../state/user/user.actions';
|
import { loadUserSuccess } from '../../state/user/user.actions';
|
||||||
import { selectUserState } from '../../state/user/user.selectors';
|
import { selectUserState } from '../../state/user/user.selectors';
|
||||||
|
|
||||||
export const authGuard: CanMatchFn = (route, state) => {
|
export const authenticationGuard: CanMatchFn = (route, state) => {
|
||||||
const authStorage: AuthStorage = inject(AuthStorage);
|
const authStorage: AuthStorage = inject(AuthStorage);
|
||||||
const authService: AuthService = inject(AuthService);
|
const authService: AuthService = inject(AuthService);
|
||||||
const userService: UserService = inject(UserService);
|
const userService: UserService = inject(UserService);
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CanMatchFn, Route, Router, UrlSegment } from '@angular/router';
|
||||||
|
import { inject } from '@angular/core';
|
||||||
|
import { map } from 'rxjs';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { User, UserState } from '../../state/user';
|
||||||
|
import { selectUser } from '../../state/user/user.selectors';
|
||||||
|
|
||||||
|
export const authorizationGuard = (authorizationCheck: (user: User) => boolean): CanMatchFn => {
|
||||||
|
return (route: Route, state: UrlSegment[]) => {
|
||||||
|
const router: Router = inject(Router);
|
||||||
|
const store: Store<UserState> = inject(Store<UserState>);
|
||||||
|
|
||||||
|
return store.select(selectUser).pipe(
|
||||||
|
map((currentUser) => {
|
||||||
|
if (authorizationCheck(currentUser)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - replace with 404 error page
|
||||||
|
return router.parseUrl('/');
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
|
@ -160,6 +160,30 @@ export class NiFiCommon {
|
||||||
return groupString + bundle.artifact;
|
return groupString + bundle.artifact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two strings.
|
||||||
|
*
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
public compareString(a: string, b: string): number {
|
||||||
|
if (a === b) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a < b ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two numbers.
|
||||||
|
*
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
public compareNumber(a: number, b: number): number {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constant regex for leading and/or trailing whitespace.
|
* Constant regex for leading and/or trailing whitespace.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -59,14 +59,23 @@ export class ExtensionTypesEffects {
|
||||||
combineLatest([
|
combineLatest([
|
||||||
this.extensionTypesService.getControllerServiceTypes(),
|
this.extensionTypesService.getControllerServiceTypes(),
|
||||||
this.extensionTypesService.getReportingTaskTypes(),
|
this.extensionTypesService.getReportingTaskTypes(),
|
||||||
|
this.extensionTypesService.getRegistryClientTypes(),
|
||||||
this.extensionTypesService.getParameterProviderTypes(),
|
this.extensionTypesService.getParameterProviderTypes(),
|
||||||
this.extensionTypesService.getFlowAnalysisRuleTypes()
|
this.extensionTypesService.getFlowAnalysisRuleTypes()
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([controllerServiceTypes, reportingTaskTypes, parameterProviderTypes, flowAnalysisRuleTypes]) =>
|
map(
|
||||||
|
([
|
||||||
|
controllerServiceTypes,
|
||||||
|
reportingTaskTypes,
|
||||||
|
registryClientTypes,
|
||||||
|
parameterProviderTypes,
|
||||||
|
flowAnalysisRuleTypes
|
||||||
|
]) =>
|
||||||
ExtensionTypesActions.loadExtensionTypesForSettingsSuccess({
|
ExtensionTypesActions.loadExtensionTypesForSettingsSuccess({
|
||||||
response: {
|
response: {
|
||||||
controllerServiceTypes: controllerServiceTypes.controllerServiceTypes,
|
controllerServiceTypes: controllerServiceTypes.controllerServiceTypes,
|
||||||
reportingTaskTypes: reportingTaskTypes.reportingTaskTypes,
|
reportingTaskTypes: reportingTaskTypes.reportingTaskTypes,
|
||||||
|
registryClientTypes: registryClientTypes.flowRegistryClientTypes,
|
||||||
parameterProviderTypes: parameterProviderTypes.parameterProviderTypes,
|
parameterProviderTypes: parameterProviderTypes.parameterProviderTypes,
|
||||||
flowAnalysisRuleTypes: flowAnalysisRuleTypes.flowAnalysisRuleTypes
|
flowAnalysisRuleTypes: flowAnalysisRuleTypes.flowAnalysisRuleTypes
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ export const initialState: ExtensionTypesState = {
|
||||||
controllerServiceTypes: [],
|
controllerServiceTypes: [],
|
||||||
prioritizerTypes: [],
|
prioritizerTypes: [],
|
||||||
reportingTaskTypes: [],
|
reportingTaskTypes: [],
|
||||||
|
registryClientTypes: [],
|
||||||
flowAnalysisRuleTypes: [],
|
flowAnalysisRuleTypes: [],
|
||||||
parameterProviderTypes: [],
|
parameterProviderTypes: [],
|
||||||
error: null,
|
error: null,
|
||||||
|
@ -54,6 +55,7 @@ export const extensionTypesReducer = createReducer(
|
||||||
...state,
|
...state,
|
||||||
controllerServiceTypes: response.controllerServiceTypes,
|
controllerServiceTypes: response.controllerServiceTypes,
|
||||||
reportingTaskTypes: response.reportingTaskTypes,
|
reportingTaskTypes: response.reportingTaskTypes,
|
||||||
|
registryClientTypes: response.registryClientTypes,
|
||||||
parameterProviderTypes: response.parameterProviderTypes,
|
parameterProviderTypes: response.parameterProviderTypes,
|
||||||
flowAnalysisRuleTypes: response.flowAnalysisRuleTypes,
|
flowAnalysisRuleTypes: response.flowAnalysisRuleTypes,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
|
@ -39,3 +39,8 @@ export const selectReportingTaskTypes = createSelector(
|
||||||
selectExtensionTypesState,
|
selectExtensionTypesState,
|
||||||
(state: ExtensionTypesState) => state.reportingTaskTypes
|
(state: ExtensionTypesState) => state.reportingTaskTypes
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectRegistryClientTypes = createSelector(
|
||||||
|
selectExtensionTypesState,
|
||||||
|
(state: ExtensionTypesState) => state.registryClientTypes
|
||||||
|
);
|
||||||
|
|
|
@ -28,6 +28,7 @@ export interface LoadExtensionTypesForCanvasResponse {
|
||||||
export interface LoadExtensionTypesForSettingsResponse {
|
export interface LoadExtensionTypesForSettingsResponse {
|
||||||
controllerServiceTypes: DocumentedType[];
|
controllerServiceTypes: DocumentedType[];
|
||||||
reportingTaskTypes: DocumentedType[];
|
reportingTaskTypes: DocumentedType[];
|
||||||
|
registryClientTypes: DocumentedType[];
|
||||||
flowAnalysisRuleTypes: DocumentedType[];
|
flowAnalysisRuleTypes: DocumentedType[];
|
||||||
parameterProviderTypes: DocumentedType[];
|
parameterProviderTypes: DocumentedType[];
|
||||||
}
|
}
|
||||||
|
@ -41,6 +42,7 @@ export interface ExtensionTypesState {
|
||||||
controllerServiceTypes: DocumentedType[];
|
controllerServiceTypes: DocumentedType[];
|
||||||
prioritizerTypes: DocumentedType[];
|
prioritizerTypes: DocumentedType[];
|
||||||
reportingTaskTypes: DocumentedType[];
|
reportingTaskTypes: DocumentedType[];
|
||||||
|
registryClientTypes: DocumentedType[];
|
||||||
flowAnalysisRuleTypes: DocumentedType[];
|
flowAnalysisRuleTypes: DocumentedType[];
|
||||||
parameterProviderTypes: DocumentedType[];
|
parameterProviderTypes: DocumentedType[];
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
|
|
@ -32,5 +32,13 @@
|
||||||
.mat-column-property {
|
.mat-column-property {
|
||||||
min-width: 230px;
|
min-width: 230px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-column-value {
|
||||||
|
min-width: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-actions {
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue