diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/registry.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/registry.service.ts new file mode 100644 index 0000000000..971fb4b455 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/registry.service.ts @@ -0,0 +1,58 @@ +/* + * 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 } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { ImportFromRegistryRequest } from '../state/flow'; + +@Injectable({ providedIn: 'root' }) +export class RegistryService { + private static readonly API: string = '../nifi-api'; + + constructor(private httpClient: HttpClient) {} + + getRegistryClients(): Observable { + return this.httpClient.get(`${RegistryService.API}/flow/registries`); + } + + getBuckets(registryId: string): Observable { + return this.httpClient.get(`${RegistryService.API}/flow/registries/${registryId}/buckets`); + } + + getFlows(registryId: string, bucketId: string): Observable { + return this.httpClient.get(`${RegistryService.API}/flow/registries/${registryId}/buckets/${bucketId}/flows`); + } + + getFlowVersions(registryId: string, bucketId: string, flowId: string): Observable { + return this.httpClient.get( + `${RegistryService.API}/flow/registries/${registryId}/buckets/${bucketId}/flows/${flowId}/versions` + ); + } + + importFromRegistry(processGroupId: string, request: ImportFromRegistryRequest): Observable { + return this.httpClient.post( + `${RegistryService.API}/process-groups/${processGroupId}/process-groups`, + request.payload, + { + params: { + parameterContextHandlingStrategy: request.keepExistingParameterContext ? 'KEEP_EXISTING' : 'REPLACE' + } + } + ); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts index 53f7f10f2d..8b79c0bcc8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts @@ -71,7 +71,9 @@ import { NavigateToQueueListing, StartProcessGroupResponse, StopProcessGroupResponse, - CenterComponentRequest + CenterComponentRequest, + ImportFromRegistryDialogRequest, + ImportFromRegistryRequest } from './index'; import { StatusHistoryRequest } from '../../../../state/status-history'; @@ -275,6 +277,16 @@ export const openNewPortDialog = createAction( export const createPort = createAction(`${CANVAS_PREFIX} Create Port`, props<{ request: CreatePortRequest }>()); +export const openImportFromRegistryDialog = createAction( + `${CANVAS_PREFIX} Open Import From Registry Dialog`, + props<{ request: ImportFromRegistryDialogRequest }>() +); + +export const importFromRegistry = createAction( + `${CANVAS_PREFIX} Import From Registry`, + props<{ request: ImportFromRegistryRequest }>() +); + export const createComponentSuccess = createAction( `${CANVAS_PREFIX} Create Component Success`, props<{ response: CreateComponentResponse }>() diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts index 05ca83d1f3..d77eb0cc08 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts @@ -40,6 +40,7 @@ import { CreateProcessGroupDialogRequest, DeleteComponentResponse, GroupComponentsDialogRequest, + ImportFromRegistryDialogRequest, LoadProcessGroupRequest, LoadProcessGroupResponse, Snippet, @@ -63,7 +64,13 @@ import { ConnectionManager } from '../../service/manager/connection-manager.serv import { MatDialog } from '@angular/material/dialog'; import { CreatePort } from '../../ui/canvas/items/port/create-port/create-port.component'; import { EditPort } from '../../ui/canvas/items/port/edit-port/edit-port.component'; -import { ComponentType } from '../../../../state/shared'; +import { + BucketEntity, + ComponentType, + RegistryClientEntity, + VersionedFlowEntity, + VersionedFlowSnapshotMetadataEntity +} from '../../../../state/shared'; import { Router } from '@angular/router'; import { Client } from '../../../../service/client.service'; import { CanvasUtils } from '../../service/canvas-utils.service'; @@ -83,6 +90,10 @@ import { ControllerServiceService } from '../../service/controller-service.servi import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component'; import { PropertyTableHelperService } from '../../../../service/property-table-helper.service'; import { ParameterHelperService } from '../../service/parameter-helper.service'; +import { RegistryService } from '../../service/registry.service'; +import { ImportFromRegistry } from '../../ui/canvas/items/flow/import-from-registry/import-from-registry.component'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import { NoRegistryClientsDialog } from '../../ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component'; @Injectable() export class FlowEffects { @@ -91,6 +102,7 @@ export class FlowEffects { private store: Store, private flowService: FlowService, private controllerServiceService: ControllerServiceService, + private registryService: RegistryService, private client: Client, private canvasUtils: CanvasUtils, private canvasView: CanvasView, @@ -217,6 +229,18 @@ export class FlowEffects { case ComponentType.InputPort: case ComponentType.OutputPort: return of(FlowActions.openNewPortDialog({ request })); + case ComponentType.Flow: + return from(this.registryService.getRegistryClients()).pipe( + map((response) => { + const dialogRequest: ImportFromRegistryDialogRequest = { + request, + registryClients: response.registries + }; + + return FlowActions.openImportFromRegistryDialog({ request: dialogRequest }); + }), + catchError((error) => of(FlowActions.flowApiError({ error: error.error }))) + ); default: return of(FlowActions.flowApiError({ error: 'Unsupported type of Component.' })); } @@ -587,6 +611,95 @@ export class FlowEffects { ) ); + openImportFromRegistryDialog$ = createEffect( + () => + this.actions$.pipe( + ofType(FlowActions.openImportFromRegistryDialog), + map((action) => action.request), + concatLatestFrom(() => this.store.select(selectCurrentUser)), + tap(([request, currentUser]) => { + const someRegistries = request.registryClients.some( + (registryClient: RegistryClientEntity) => registryClient.permissions.canRead + ); + + if (someRegistries) { + const dialogReference = this.dialog.open(ImportFromRegistry, { + data: request, + panelClass: 'medium-dialog' + }); + + dialogReference.componentInstance.getBuckets = ( + registryId: string + ): Observable => { + return this.registryService.getBuckets(registryId).pipe( + take(1), + map((response) => response.buckets) + ); + }; + + dialogReference.componentInstance.getFlows = ( + registryId: string, + bucketId: string + ): Observable => { + return this.registryService.getFlows(registryId, bucketId).pipe( + take(1), + map((response) => response.versionedFlows) + ); + }; + + dialogReference.componentInstance.getFlowVersions = ( + registryId: string, + bucketId: string, + flowId: string + ): Observable => { + return this.registryService.getFlowVersions(registryId, bucketId, flowId).pipe( + take(1), + map((response) => response.versionedFlowSnapshotMetadataSet) + ); + }; + + dialogReference.afterClosed().subscribe(() => { + this.store.dispatch(FlowActions.setDragging({ dragging: false })); + }); + } else { + this.dialog + .open(NoRegistryClientsDialog, { + data: { + controllerPermissions: currentUser.controllerPermissions + }, + panelClass: 'medium-dialog' + }) + .afterClosed() + .subscribe(() => { + this.store.dispatch(FlowActions.setDragging({ dragging: false })); + }); + } + }) + ), + { dispatch: false } + ); + + importFromRegistry$ = createEffect(() => + this.actions$.pipe( + ofType(FlowActions.importFromRegistry), + map((action) => action.request), + concatLatestFrom(() => this.store.select(selectCurrentProcessGroupId)), + switchMap(([request, processGroupId]) => + from(this.registryService.importFromRegistry(processGroupId, request)).pipe( + map((response) => + FlowActions.createComponentSuccess({ + response: { + type: ComponentType.ProcessGroup, + payload: response + } + }) + ), + catchError((error) => of(FlowActions.flowApiError({ error: error.error }))) + ) + ) + ) + ); + createComponentSuccess$ = createEffect(() => this.actions$.pipe( ofType(FlowActions.createComponentSuccess), diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/index.ts index 1352c36e2b..bb4f9ddb70 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/index.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/index.ts @@ -23,6 +23,7 @@ import { DocumentedType, ParameterContextReferenceEntity, Permissions, + RegistryClientEntity, Revision, SelectOption } from '../../../../state/shared'; @@ -165,6 +166,20 @@ export interface CreateProcessGroupDialogRequest { parameterContexts: ParameterContextEntity[]; } +export interface NoRegistryClientsDialogRequest { + controllerPermissions: Permissions; +} + +export interface ImportFromRegistryDialogRequest { + request: CreateComponentRequest; + registryClients: RegistryClientEntity[]; +} + +export interface ImportFromRegistryRequest { + payload: any; + keepExistingParameterContext: boolean; +} + export interface OpenGroupComponentsDialogRequest { position: Position; moveComponents: MoveComponentRequest[]; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.html index 963b88a462..2eb6996f03 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.html @@ -48,6 +48,11 @@ [disabled]="!canvasPermissions.canWrite" iconClass="icon-funnel" iconHoverClass="icon-funnel-add"> + + +

Import From Registry

+
+ + + + Registry + + + + {{ option.text }} + + + {{ option.text }} + + + + + + Bucket + + + + {{ option.text }} + + + {{ option.text }} + + + + No buckets available + + + Flow + + + + {{ option.text }} + + + {{ option.text }} + + + + No flows available + +
+ + Keep existing Parameter Contexts + +
+
+
Flow Description
+
{{ selectedFlowDescription || 'No description provided' }}
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Version + {{ item.version }} + Created + {{ formatTimestamp(item) }} + Comments + {{ item.comments }} +
+
+
+
+ + + + +
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/import-from-registry/import-from-registry.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/import-from-registry/import-from-registry.component.scss new file mode 100644 index 0000000000..bba2634bef --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/import-from-registry/import-from-registry.component.scss @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@use '@angular/material' as mat; + +.import-from-registry-form { + @include mat.button-density(-1); + + .mat-mdc-form-field { + width: 100%; + } + + .mat-mdc-form-field-error { + font-size: 12px; + } + + .listing-table { + table { + width: auto; + table-layout: unset; + + .mat-column-version { + width: 75px; + } + + .mat-column-created { + width: 200px; + } + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/import-from-registry/import-from-registry.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/import-from-registry/import-from-registry.component.spec.ts new file mode 100644 index 0000000000..bdcb15836e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/import-from-registry/import-from-registry.component.spec.ts @@ -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 { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ImportFromRegistry } from './import-from-registry.component'; +import { ImportFromRegistryDialogRequest } from '../../../../../state/flow'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ComponentType } from '../../../../../../../state/shared'; +import { provideMockStore } from '@ngrx/store/testing'; +import { initialState } from '../../../../../state/flow/flow.reducer'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { EMPTY } from 'rxjs'; + +describe('ImportFromRegistry', () => { + let component: ImportFromRegistry; + let fixture: ComponentFixture; + + const data: ImportFromRegistryDialogRequest = { + request: { + revision: { + clientId: '88cd6620-bd6d-41fa-aa5a-be2b33501e31', + version: 0 + }, + type: ComponentType.Flow, + position: { + x: 461, + y: 58 + } + }, + registryClients: [ + { + revision: { + version: 0 + }, + id: '6a088515-018d-1000-ce79-5ae44266bc20', + uri: 'https://localhost:4200/nifi-api/controller/registry-clients/6a088515-018d-1000-ce79-5ae44266bc20', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: '6a088515-018d-1000-ce79-5ae44266bc20', + name: 'My 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: 'http://localhost:18080/nifi-registry', + '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: [], + 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, + validationStatus: 'VALID', + multipleVersionsAvailable: false, + extensionMissing: false + } + } + ] + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ImportFromRegistry, BrowserAnimationsModule], + providers: [{ provide: MAT_DIALOG_DATA, useValue: data }, provideMockStore({ initialState })] + }); + fixture = TestBed.createComponent(ImportFromRegistry); + component = fixture.componentInstance; + + component.getBuckets = () => { + return EMPTY; + }; + component.getFlows = () => { + return EMPTY; + }; + component.getFlowVersions = () => { + return EMPTY; + }; + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/import-from-registry/import-from-registry.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/import-from-registry/import-from-registry.component.ts new file mode 100644 index 0000000000..3496b0512f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/import-from-registry/import-from-registry.component.ts @@ -0,0 +1,337 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Inject, Input, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { ImportFromRegistryDialogRequest } from '../../../../../state/flow'; +import { Store } from '@ngrx/store'; +import { CanvasState } from '../../../../../state'; +import { + BucketEntity, + isDefinedAndNotNull, + RegistryClientEntity, + SelectOption, + TextTipInput, + VersionedFlow, + VersionedFlowEntity, + VersionedFlowSnapshotMetadata, + VersionedFlowSnapshotMetadataEntity +} from '../../../../../../../state/shared'; +import { selectSaving } from '../../../../../state/flow/flow.selectors'; +import { AsyncPipe, JsonPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; +import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-banner.component'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatOptionModule } from '@angular/material/core'; +import { MatSelectModule } from '@angular/material/select'; +import { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nifi-spinner.directive'; +import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { TextTip } from '../../../../../../../ui/common/tooltips/text-tip/text-tip.component'; +import { NifiTooltipDirective } from '../../../../../../../ui/common/tooltips/nifi-tooltip.directive'; +import { MatIconModule } from '@angular/material/icon'; +import { Observable, take } from 'rxjs'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { MatSortModule, Sort } from '@angular/material/sort'; +import { NiFiCommon } from '../../../../../../../service/nifi-common.service'; +import { selectTimeOffset } from '../../../../../../../state/flow-configuration/flow-configuration.selectors'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { Client } from '../../../../../../../service/client.service'; +import { importFromRegistry } from '../../../../../state/flow/flow.actions'; + +@Component({ + selector: 'import-from-registry', + standalone: true, + imports: [ + AsyncPipe, + ErrorBanner, + MatButtonModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + NgIf, + NifiSpinnerDirective, + ReactiveFormsModule, + MatOptionModule, + MatSelectModule, + NgForOf, + NifiTooltipDirective, + MatIconModule, + NgTemplateOutlet, + JsonPipe, + MatCheckboxModule, + MatSortModule, + MatTableModule + ], + templateUrl: './import-from-registry.component.html', + styleUrls: ['./import-from-registry.component.scss'] +}) +export class ImportFromRegistry implements OnInit { + @Input() getBuckets!: (registryId: string) => Observable; + @Input() getFlows!: (registryId: string, bucketId: string) => Observable; + @Input() getFlowVersions!: ( + registryId: string, + bucketId: string, + flowId: string + ) => Observable; + + saving$ = this.store.select(selectSaving); + timeOffset = 0; + + protected readonly TextTip = TextTip; + + importFromRegistryForm: FormGroup; + registryClientOptions: SelectOption[] = []; + bucketOptions: SelectOption[] = []; + flowOptions: SelectOption[] = []; + + flowLookup: Map = new Map(); + selectedFlowDescription: string | undefined; + + sort: Sort = { + active: 'version', + direction: 'desc' + }; + displayedColumns: string[] = ['version', 'created', 'comments']; + dataSource: MatTableDataSource = + new MatTableDataSource(); + selectedFlowVersion: number | null = null; + + constructor( + @Inject(MAT_DIALOG_DATA) private dialogRequest: ImportFromRegistryDialogRequest, + private formBuilder: FormBuilder, + private store: Store, + private nifiCommon: NiFiCommon, + private client: Client + ) { + this.store + .select(selectTimeOffset) + .pipe(isDefinedAndNotNull(), takeUntilDestroyed()) + .subscribe((timeOffset: number) => { + this.timeOffset = timeOffset; + }); + + const sortedRegistries = dialogRequest.registryClients.slice().sort((a, b) => { + return this.nifiCommon.compareString(a.component.name, b.component.name); + }); + + sortedRegistries.forEach((registryClient: RegistryClientEntity) => { + if (registryClient.permissions.canRead) { + this.registryClientOptions.push({ + text: registryClient.component.name, + value: registryClient.id, + description: registryClient.component.description + }); + } + }); + + this.importFromRegistryForm = this.formBuilder.group({ + registry: new FormControl(this.registryClientOptions[0].value, Validators.required), + bucket: new FormControl(null, Validators.required), + flow: new FormControl(null, Validators.required), + keepParameterContexts: new FormControl(true, Validators.required) + }); + } + + ngOnInit(): void { + const selectedRegistryId = this.importFromRegistryForm.get('registry')?.value; + + if (selectedRegistryId) { + this.loadBuckets(selectedRegistryId); + } + } + + getSelectOptionTipData(option: SelectOption): TextTipInput { + return { + // @ts-ignore + text: option.description + }; + } + + registryChanged(registryId: string): void { + this.loadBuckets(registryId); + } + + bucketChanged(bucketId: string): void { + const registryId = this.importFromRegistryForm.get('registry')?.value; + this.loadFlows(registryId, bucketId); + } + + flowChanged(flowId: string): void { + const registryId = this.importFromRegistryForm.get('registry')?.value; + const bucketId = this.importFromRegistryForm.get('bucket')?.value; + this.loadVersions(registryId, bucketId, flowId); + } + + loadBuckets(registryId: string): void { + this.bucketOptions = []; + + this.getBuckets(registryId) + .pipe(take(1)) + .subscribe((buckets: BucketEntity[]) => { + if (buckets.length > 0) { + buckets.forEach((entity: BucketEntity) => { + if (entity.permissions.canRead) { + this.bucketOptions.push({ + text: entity.bucket.name, + value: entity.id, + description: entity.bucket.description + }); + } + }); + + const bucketId = this.bucketOptions[0].value; + if (bucketId) { + this.importFromRegistryForm.get('bucket')?.setValue(bucketId); + this.loadFlows(registryId, bucketId); + } + } + }); + } + + loadFlows(registryId: string, bucketId: string): void { + this.flowOptions = []; + this.flowLookup.clear(); + + this.getFlows(registryId, bucketId) + .pipe(take(1)) + .subscribe((versionedFlows: VersionedFlowEntity[]) => { + if (versionedFlows.length > 0) { + versionedFlows.forEach((entity: VersionedFlowEntity) => { + this.flowLookup.set(entity.versionedFlow.flowId, entity.versionedFlow); + + this.flowOptions.push({ + text: entity.versionedFlow.flowName, + value: entity.versionedFlow.flowId, + description: entity.versionedFlow.description + }); + }); + + const flowId = this.flowOptions[0].value; + if (flowId) { + this.importFromRegistryForm.get('flow')?.setValue(flowId); + this.loadVersions(registryId, bucketId, flowId); + } + } + }); + } + + loadVersions(registryId: string, bucketId: string, flowId: string): void { + this.dataSource.data = []; + this.selectedFlowDescription = this.flowLookup.get(flowId)?.description; + + this.getFlowVersions(registryId, bucketId, flowId) + .pipe(take(1)) + .subscribe((metadataEntities: VersionedFlowSnapshotMetadataEntity[]) => { + if (metadataEntities.length > 0) { + const flowVersions = metadataEntities.map( + (entity: VersionedFlowSnapshotMetadataEntity) => entity.versionedFlowSnapshotMetadata + ); + + const sortedFlowVersions = this.sortVersions(flowVersions, this.sort); + this.selectedFlowVersion = sortedFlowVersions[0].version; + + this.dataSource.data = sortedFlowVersions; + } + }); + } + + formatTimestamp(flowVersion: VersionedFlowSnapshotMetadata) { + // get the current user time to properly convert the server time + const now: Date = new Date(); + + // convert the user offset to millis + const userTimeOffset: number = now.getTimezoneOffset() * 60 * 1000; + + // create the proper date by adjusting by the offsets + const date: Date = new Date(flowVersion.timestamp + userTimeOffset + this.timeOffset); + return this.nifiCommon.formatDateTime(date); + } + + sortData(sort: Sort) { + this.sort = sort; + this.dataSource.data = this.sortVersions(this.dataSource.data, sort); + } + + sortVersions(data: VersionedFlowSnapshotMetadata[], sort: Sort): VersionedFlowSnapshotMetadata[] { + if (!data) { + return []; + } + return data.slice().sort((a, b) => { + const isAsc = sort.direction === 'asc'; + let retVal = 0; + switch (sort.active) { + case 'version': + retVal = this.nifiCommon.compareNumber(a.version, b.version); + break; + case 'created': + retVal = this.nifiCommon.compareNumber(a.timestamp, b.timestamp); + break; + case 'comments': + retVal = this.nifiCommon.compareString(a.comments, b.comments); + break; + } + return retVal * (isAsc ? 1 : -1); + }); + } + + select(flowVersion: VersionedFlowSnapshotMetadata): void { + this.selectedFlowVersion = flowVersion.version; + } + + isSelected(flowVersion: VersionedFlowSnapshotMetadata): boolean { + if (this.selectedFlowVersion) { + return flowVersion.version == this.selectedFlowVersion; + } + return false; + } + + importFromRegistry(): void { + if (this.selectedFlowVersion != null) { + const payload: any = { + revision: this.client.getRevision({ + revision: { + version: 0 + } + }), + // disconnectedNodeAcknowledged: nfStorage.isDisconnectionAcknowledged(), + component: { + position: { + x: this.dialogRequest.request.position.x, + y: this.dialogRequest.request.position.y + }, + versionControlInformation: { + registryId: this.importFromRegistryForm.get('registry')?.value, + bucketId: this.importFromRegistryForm.get('bucket')?.value, + flowId: this.importFromRegistryForm.get('flow')?.value, + version: this.selectedFlowVersion + } + } + }; + + this.store.dispatch( + importFromRegistry({ + request: { + payload, + keepExistingParameterContext: this.importFromRegistryForm.get('keepParameterContexts')?.value + } + }) + ); + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.html new file mode 100644 index 0000000000..e6fa0fc55c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.html @@ -0,0 +1,38 @@ + + +

No Registry clients available

+ +
+ {{ + request.controllerPermissions.canRead + ? 'In order to import a flow a Registry Client must be configured in Controller Settings.' + : 'Cannot import a Flow because there are no available Registry Clients.' + }} +
+
+ + + + + + + + + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.scss new file mode 100644 index 0000000000..fc444ed325 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.scss @@ -0,0 +1,22 @@ +/* + * 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; + +mat-dialog-actions { + @include mat.button-density(-1); +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.spec.ts new file mode 100644 index 0000000000..64117f14a8 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.spec.ts @@ -0,0 +1,50 @@ +/* + * 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 { NoRegistryClientsDialog } from './no-registry-clients-dialog.component'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; + +describe('NoRegistryClientsDialog', () => { + let component: NoRegistryClientsDialog; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [NoRegistryClientsDialog], + providers: [ + { + provide: MAT_DIALOG_DATA, + useValue: { + controllerPermissions: { + canRead: true, + canWrite: true + } + } + } + ] + }); + fixture = TestBed.createComponent(NoRegistryClientsDialog); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.ts new file mode 100644 index 0000000000..f7897b2904 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component.ts @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; +import { NoRegistryClientsDialogRequest } from '../../../state/flow'; +import { NgIf } from '@angular/common'; +import { RouterLink } from '@angular/router'; + +@Component({ + selector: 'no-registry-clients-dialog', + standalone: true, + imports: [MatDialogModule, MatButtonModule, NgIf, RouterLink], + templateUrl: './no-registry-clients-dialog.component.html', + styleUrls: ['./no-registry-clients-dialog.component.scss'] +}) +export class NoRegistryClientsDialog { + constructor(@Inject(MAT_DIALOG_DATA) public request: NoRegistryClientsDialogRequest) {} +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/registry-client.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/registry-client.service.ts index 94ed22fd26..514043c7a7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/registry-client.service.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/registry-client.service.ts @@ -23,10 +23,9 @@ import { NiFiCommon } from '../../../service/nifi-common.service'; import { CreateRegistryClientRequest, DeleteRegistryClientRequest, - EditRegistryClientRequest, - RegistryClientEntity + EditRegistryClientRequest } from '../state/registry-clients'; -import { PropertyDescriptorRetriever } from '../../../state/shared'; +import { PropertyDescriptorRetriever, RegistryClientEntity } from '../../../state/shared'; @Injectable({ providedIn: 'root' }) export class RegistryClientService implements PropertyDescriptorRetriever { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/index.ts index 4cbe47e3ee..7d8ff8bdae 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/index.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/index.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { BulletinEntity, DocumentedType, Permissions, Revision } from '../../../../state/shared'; +import { DocumentedType, RegistryClientEntity, Revision } from '../../../../state/shared'; export const registryClientsFeatureKey = 'registryClients'; @@ -70,16 +70,6 @@ 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; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/registry-clients.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/registry-clients.selectors.ts index 49dcbd5ecf..b98e007ae3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/registry-clients.selectors.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/registry-clients.selectors.ts @@ -18,7 +18,8 @@ import { createSelector } from '@ngrx/store'; import { selectSettingsState, SettingsState } from '../index'; import { selectCurrentRoute } from '../../../../state/router/router.selectors'; -import { RegistryClientEntity, registryClientsFeatureKey, RegistryClientsState } from './index'; +import { registryClientsFeatureKey, RegistryClientsState } from './index'; +import { RegistryClientEntity } from '../../../../state/shared'; export const selectRegistryClientsState = createSelector( selectSettingsState, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.ts index 7b05aa0351..c0384e2caf 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.ts @@ -23,8 +23,7 @@ import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.com 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'; +import { BulletinsTipInput, RegistryClientEntity, ValidationErrorsTipInput } from '../../../../../state/shared'; @Component({ selector: 'registry-client-table', diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/registry-clients/registry-clients.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/registry-clients/registry-clients.component.ts index 55690c4bfb..ef64be5cc3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/registry-clients/registry-clients.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/registry-clients/registry-clients.component.ts @@ -32,12 +32,13 @@ import { resetRegistryClientsState, selectClient } from '../../state/registry-clients/registry-clients.actions'; -import { RegistryClientEntity, RegistryClientsState } from '../../state/registry-clients'; +import { RegistryClientsState } from '../../state/registry-clients'; import { initialState } from '../../state/registry-clients/registry-clients.reducer'; import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { NiFiState } from '../../../../state'; import { filter, switchMap, take } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { RegistryClientEntity } from '../../../../state/shared'; @Component({ selector: 'registry-clients', diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/flow-configuration/flow-configuration.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/flow-configuration/flow-configuration.selectors.ts index 458bf59dff..f9a49749c7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/flow-configuration/flow-configuration.selectors.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/flow-configuration/flow-configuration.selectors.ts @@ -29,3 +29,8 @@ export const selectSupportsManagedAuthorizer = createSelector( selectFlowConfiguration, (flowConfiguration: FlowConfiguration | null) => flowConfiguration?.supportsManagedAuthorizer ); + +export const selectTimeOffset = createSelector( + selectFlowConfiguration, + (flowConfiguration: FlowConfiguration | null) => flowConfiguration?.timeOffset +); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts index aec81d5933..63eed665d9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts @@ -386,7 +386,8 @@ export enum ComponentType { ReportingTask = 'ReportingTask', FlowAnalysisRule = 'FlowAnalysisRule', ParameterProvider = 'ParameterProvider', - FlowRegistryClient = 'FlowRegistryClient' + FlowRegistryClient = 'FlowRegistryClient', + Flow = 'Flow' } export interface ControllerServiceReferencingComponent { @@ -481,6 +482,57 @@ export interface AllowableValueEntity { allowableValue: AllowableValue; } +export interface RegistryClientEntity { + permissions: Permissions; + operatePermissions?: Permissions; + revision: Revision; + bulletins?: BulletinEntity[]; + id: string; + uri: string; + component: any; +} + +export interface BucketEntity { + id: string; + permissions: Permissions; + bucket: Bucket; +} + +export interface Bucket { + created: number; + description: string; + id: string; + name: string; +} + +export interface VersionedFlowEntity { + versionedFlow: VersionedFlow; +} + +export interface VersionedFlow { + registryId: string; + bucketId: string; + flowId: string; + flowName: string; + description: string; + comments: string; + action: string; +} + +export interface VersionedFlowSnapshotMetadataEntity { + registryId: string; + versionedFlowSnapshotMetadata: VersionedFlowSnapshotMetadata; +} + +export interface VersionedFlowSnapshotMetadata { + bucketIdentifier: string; + flowIdentifier: string; + version: number; + timestamp: number; + author: string; + comments: string; +} + export interface SelectOption { text: string; value: string | null; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/ok-dialog/ok-dialog.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/ok-dialog/ok-dialog.component.html index 76f19dd22d..945c2f7395 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/ok-dialog/ok-dialog.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/ok-dialog/ok-dialog.component.html @@ -17,7 +17,7 @@

{{ request.title }}

-
{{ request.message }}
+
{{ request.message }}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.css old mode 100755 new mode 100644 index 76585dbab7..826fce7576 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.css @@ -1,12 +1,12 @@ @font-face { font-family: 'flowfont'; - src: url('./flowfont.eot?33669169'); + src: url('./flowfont.eot?8516181'); src: - url('./flowfont.eot?33669169#iefix') format('embedded-opentype'), - url('./flowfont.woff2?33669169') format('woff2'), - url('./flowfont.woff?33669169') format('woff'), - url('./flowfont.ttf?33669169') format('truetype'), - url('./flowfont.svg?33669169#flowfont') format('svg'); + url('./flowfont.eot?8516181#iefix') format('embedded-opentype'), + url('./flowfont.woff2?8516181') format('woff2'), + url('./flowfont.woff?8516181') format('woff'), + url('./flowfont.ttf?8516181') format('truetype'), + url('./flowfont.svg?8516181#flowfont') format('svg'); font-weight: normal; font-style: normal; } @@ -16,11 +16,10 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'flowfont'; - src: url('../font/flowfont.svg?33669169#flowfont') format('svg'); + src: url('../font/flowfont.svg?8516181#flowfont') format('svg'); } } */ - [class^='icon-']:before, [class*=' icon-']:before { font-family: 'flowfont'; @@ -126,6 +125,12 @@ .icon-lineage:before { content: '\e816'; } /* '' */ +.icon-import-from-registry-add:before { + content: '\e81d'; +} /* '' */ +.icon-import-from-registry:before { + content: '\e81e'; +} /* '' */ .icon-port-in:before { content: '\e832'; } /* '' */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.eot b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.eot old mode 100755 new mode 100644 index 48a3ab1cd4..f0d0fe0410 Binary files a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.eot and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.eot differ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.svg b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.svg old mode 100755 new mode 100644 index 4b7cd8a84d..5427d62811 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.svg +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.svg @@ -1,68 +1,72 @@ -Copyright (C) 2016 by original authors @ fontello.com +Copyright (C) 2023 by original authors @ fontello.com - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + - \ No newline at end of file + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.ttf b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.ttf old mode 100755 new mode 100644 index 519f7cbf10..c16425cc28 Binary files a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.ttf and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.ttf differ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.woff b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.woff old mode 100755 new mode 100644 index eea3656405..799454c3b2 Binary files a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.woff and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.woff differ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.woff2 b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.woff2 old mode 100755 new mode 100644 index 0138d2a8e5..1917ea71cd Binary files a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.woff2 and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/fonts/flowfont/flowfont.woff2 differ