diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts index 81a378761d..7dbe02cc1b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts @@ -47,6 +47,11 @@ const routes: Routes = [ canMatch: [authenticationGuard], loadChildren: () => import('./pages/counters/feature/counters.module').then((m) => m.CountersModule) }, + { + path: 'users', + canMatch: [authenticationGuard], + loadChildren: () => import('./pages/users/feature/users.module').then((m) => m.UsersModule) + }, { path: 'summary', canMatch: [authenticationGuard], diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts index 18ebf2f33e..da836bc227 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts @@ -27,7 +27,7 @@ import { environment } from './environments/environment'; import { HTTP_INTERCEPTORS, HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'; import { NavigationActionTiming, RouterState, StoreRouterConnectingModule } from '@ngrx/router-store'; import { rootReducers } from './state'; -import { UserEffects } from './state/user/user.effects'; +import { CurrentUserEffects } from './state/current-user/current-user.effects'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { LoadingInterceptor } from './service/interceptors/loading.interceptor'; import { AuthInterceptor } from './service/interceptors/auth.interceptor'; @@ -59,7 +59,7 @@ import { SystemDiagnosticsEffects } from './state/system-diagnostics/system-diag navigationActionTiming: NavigationActionTiming.PostActivation }), EffectsModule.forRoot( - UserEffects, + CurrentUserEffects, ExtensionTypesEffects, AboutEffects, StatusHistoryEffects, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters-routing.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters-routing.module.ts index 71ef2068f4..2d86a1f767 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters-routing.module.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters-routing.module.ts @@ -19,13 +19,13 @@ import { RouterModule, Routes } from '@angular/router'; import { NgModule } from '@angular/core'; import { Counters } from './counters.component'; import { authorizationGuard } from '../../../service/guard/authorization.guard'; -import { User } from '../../../state/user'; +import { CurrentUser } from '../../../state/current-user'; const routes: Routes = [ { path: '', component: Counters, - canMatch: [authorizationGuard((user: User) => user.countersPermissions.canRead)] + canMatch: [authorizationGuard((user: CurrentUser) => user.countersPermissions.canRead)] } ]; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters.component.ts index 241bfef9e3..5d4a28ecf2 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters.component.ts @@ -18,7 +18,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { NiFiState } from '../../../state'; -import { startUserPolling, stopUserPolling } from '../../../state/user/user.actions'; +import { startCurrentUserPolling, stopCurrentUserPolling } from '../../../state/current-user/current-user.actions'; import { resetCounterState } from '../state/counter-listing/counter-listing.actions'; @Component({ @@ -30,11 +30,11 @@ export class Counters implements OnInit, OnDestroy { constructor(private store: Store) {} ngOnInit(): void { - this.store.dispatch(startUserPolling()); + this.store.dispatch(startCurrentUserPolling()); } ngOnDestroy(): void { this.store.dispatch(resetCounterState()); - this.store.dispatch(stopUserPolling()); + this.store.dispatch(stopCurrentUserPolling()); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters.module.ts index b29f003a77..0d4d5d8939 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters.module.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/feature/counters.module.ts @@ -24,7 +24,6 @@ import { countersFeatureKey, reducers } from '../state'; import { EffectsModule } from '@ngrx/effects'; import { CounterListingEffects } from '../state/counter-listing/counter-listing.effects'; import { CounterListingModule } from '../ui/counter-listing/counter-listing.module'; -import { ParameterContextListingModule } from '../../parameter-contexts/ui/parameter-context-listing/parameter-context-listing.module'; import { MatDialogModule } from '@angular/material/dialog'; @NgModule({ @@ -36,7 +35,6 @@ import { MatDialogModule } from '@angular/material/dialog'; StoreModule.forFeature(countersFeatureKey, reducers), EffectsModule.forFeature(CounterListingEffects), CounterListingModule, - ParameterContextListingModule, MatDialogModule ] }) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/state/counter-listing/counter-listing.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/state/counter-listing/counter-listing.reducer.ts index 27e5090b62..c14d9cc316 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/state/counter-listing/counter-listing.reducer.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/state/counter-listing/counter-listing.reducer.ts @@ -17,8 +17,13 @@ import { CounterListingState } from './index'; import { createReducer, on } from '@ngrx/store'; -import { loadCounters, loadCountersSuccess, resetCounterState, resetCounterSuccess } from './counter-listing.actions'; -import { parameterContextListingApiError } from '../../../parameter-contexts/state/parameter-context-listing/parameter-context-listing.actions'; +import { + counterListingApiError, + loadCounters, + loadCountersSuccess, + resetCounterState, + resetCounterSuccess +} from './counter-listing.actions'; import { produce } from 'immer'; export const initialState: CounterListingState = { @@ -42,7 +47,7 @@ export const counterListingReducer = createReducer( error: null, status: 'success' as const })), - on(parameterContextListingApiError, (state, { error }) => ({ + on(counterListingApiError, (state, { error }) => ({ ...state, saving: false, error, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-listing.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-listing.component.ts index 3cf0994b49..affe277893 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-listing.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-listing.component.ts @@ -21,7 +21,7 @@ import { Store } from '@ngrx/store'; import { loadCounters, promptCounterReset } from '../../state/counter-listing/counter-listing.actions'; import { selectCounterListingState } from '../../state/counter-listing/counter-listing.selectors'; import { initialState } from '../../state/counter-listing/counter-listing.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; @Component({ selector: 'counter-listing', @@ -30,7 +30,7 @@ import { selectUser } from '../../../../state/user/user.selectors'; }) export class CounterListing implements OnInit { counterListingState$ = this.store.select(selectCounterListingState); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); constructor(private store: Store) {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer.component.ts index 43a9e6eed7..4d2f43dea6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer.component.ts @@ -17,7 +17,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; -import { startUserPolling, stopUserPolling } from '../../../state/user/user.actions'; +import { startCurrentUserPolling, stopCurrentUserPolling } from '../../../state/current-user/current-user.actions'; import { loadExtensionTypesForCanvas } from '../../../state/extension-types/extension-types.actions'; import { NiFiState } from '../../../state'; @@ -30,11 +30,11 @@ export class FlowDesigner implements OnInit, OnDestroy { constructor(private store: Store) {} ngOnInit(): void { - this.store.dispatch(startUserPolling()); + this.store.dispatch(startCurrentUserPolling()); this.store.dispatch(loadExtensionTypesForCanvas()); } ngOnDestroy(): void { - this.store.dispatch(stopUserPolling()); + this.store.dispatch(stopCurrentUserPolling()); } } 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/behavior/connectable-behavior.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/connectable-behavior.service.spec.ts index 770425e0c1..21a49215da 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/connectable-behavior.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/connectable-behavior.service.spec.ts @@ -27,8 +27,8 @@ import { CanvasState } from '../../state'; import { transformFeatureKey } from '../../state/transform'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -53,7 +53,7 @@ describe('ConnectableBehavior', () => { value: initialState[flowFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/behavior/draggable-behavior.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/draggable-behavior.service.spec.ts index 4ba0463a09..abf24c1485 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/draggable-behavior.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/draggable-behavior.service.spec.ts @@ -28,8 +28,8 @@ import { transformFeatureKey } from '../../state/transform'; import { selectFlowState } from '../../state/flow/flow.selectors'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('DraggableBehavior', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/behavior/editable-behavior.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/editable-behavior.service.spec.ts index e4f3039d67..bf1a38ea8a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/editable-behavior.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/editable-behavior.service.spec.ts @@ -28,8 +28,8 @@ import * as fromTransform from '../../state/transform/transform.reducer'; import { selectTransform } from '../../state/transform/transform.selectors'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('EditableBehaviorService', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/behavior/quick-select-behavior.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/quick-select-behavior.service.spec.ts index 84db152c65..b9399b19cd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/quick-select-behavior.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/quick-select-behavior.service.spec.ts @@ -27,8 +27,8 @@ import { transformFeatureKey } from '../../state/transform'; import * as fromTransform from '../../state/transform/transform.reducer'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -53,7 +53,7 @@ describe('QuickSelectBehavior', () => { value: initialState[flowFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/behavior/selectable-behavior.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/selectable-behavior.service.spec.ts index 4ec93666ab..05a4f80321 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/selectable-behavior.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/selectable-behavior.service.spec.ts @@ -26,8 +26,8 @@ import { provideMockStore } from '@ngrx/store/testing'; import { selectFlowState } from '../../state/flow/flow.selectors'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -52,7 +52,7 @@ describe('SelectableBehavior', () => { value: initialState[flowFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/birdseye-view.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/birdseye-view.service.spec.ts index 0c9a1ac365..235318deb7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/birdseye-view.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/birdseye-view.service.spec.ts @@ -28,8 +28,8 @@ import { selectFlowState } from '../state/flow/flow.selectors'; import { selectTransform } from '../state/transform/transform.selectors'; import { controllerServicesFeatureKey } from '../state/controller-services'; import * as fromControllerServices from '../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../state/user/user.selectors'; -import * as fromUser from '../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../state/parameter'; import * as fromParameter from '../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('BirdseyeView', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.spec.ts index 0b8bace3b5..26bb27db85 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.spec.ts @@ -27,8 +27,8 @@ import { provideMockStore } from '@ngrx/store/testing'; import { selectFlowState } from '../state/flow/flow.selectors'; import { controllerServicesFeatureKey } from '../state/controller-services'; import * as fromControllerServices from '../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../state/user/user.selectors'; -import * as fromUser from '../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../state/parameter'; import * as fromParameter from '../state/parameter/parameter.reducer'; @@ -53,7 +53,7 @@ describe('CanvasUtils', () => { value: initialState[flowFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts index 5aa09cf287..7fa34099c6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts @@ -32,9 +32,9 @@ import { BulletinsTip } from '../../../ui/common/tooltips/bulletins-tip/bulletin import { Position } from '../state/shared'; import { ComponentType, Permissions } from '../../../state/shared'; import { NiFiCommon } from '../../../service/nifi-common.service'; -import { User } from '../../../state/user'; -import { initialState as initialUserState } from '../../../state/user/user.reducer'; -import { selectUser } from '../../../state/user/user.selectors'; +import { CurrentUser } from '../../../state/current-user'; +import { initialState as initialUserState } from '../../../state/current-user/current-user.reducer'; +import { selectCurrentUser } from '../../../state/current-user/current-user.selectors'; @Injectable({ providedIn: 'root' @@ -48,7 +48,7 @@ export class CanvasUtils { private currentProcessGroupId: string = initialFlowState.id; private parentProcessGroupId: string | null = initialFlowState.flow.processGroupFlow.parentGroupId; private canvasPermissions: Permissions = initialFlowState.flow.permissions; - private currentUser: User = initialUserState.user; + private currentUser: CurrentUser = initialUserState.user; private connections: any[] = []; private readonly humanizeDuration: Humanizer; @@ -89,7 +89,7 @@ export class CanvasUtils { }); this.store - .select(selectUser) + .select(selectCurrentUser) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.currentUser = user; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-view.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-view.service.spec.ts index 4e3065382c..0f90774ceb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-view.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-view.service.spec.ts @@ -28,8 +28,8 @@ import { selectFlowState } from '../state/flow/flow.selectors'; import { selectTransform } from '../state/transform/transform.selectors'; import { controllerServicesFeatureKey } from '../state/controller-services'; import * as fromControllerServices from '../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../state/user/user.selectors'; -import * as fromUser from '../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../state/parameter'; import * as fromParameter from '../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('CanvasView', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/manager/connection-manager.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.spec.ts index 83f79ffa7a..991223e6d9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.spec.ts @@ -28,8 +28,8 @@ import { selectFlowState } from '../../state/flow/flow.selectors'; import { selectTransform } from '../../state/transform/transform.selectors'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('ConnectionManager', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/manager/funnel-manager.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/funnel-manager.service.spec.ts index d5cfa55708..ae48fc3e68 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/funnel-manager.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/funnel-manager.service.spec.ts @@ -28,8 +28,8 @@ import { selectFlowState } from '../../state/flow/flow.selectors'; import { selectTransform } from '../../state/transform/transform.selectors'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('FunnelManager', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/manager/label-manager.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.spec.ts index f44b2b2e69..2876e24c06 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.spec.ts @@ -28,8 +28,8 @@ import { selectFlowState } from '../../state/flow/flow.selectors'; import { selectTransform } from '../../state/transform/transform.selectors'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('LabelManager', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/manager/port-manager.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/port-manager.service.spec.ts index cd920ea3fb..91c78137d5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/port-manager.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/port-manager.service.spec.ts @@ -28,8 +28,8 @@ import { selectFlowState } from '../../state/flow/flow.selectors'; import { selectTransform } from '../../state/transform/transform.selectors'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('PortManager', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/manager/process-group-manager.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/process-group-manager.service.spec.ts index 82f35f7404..fd45d417d9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/process-group-manager.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/process-group-manager.service.spec.ts @@ -28,8 +28,8 @@ import { selectFlowState } from '../../state/flow/flow.selectors'; import { selectTransform } from '../../state/transform/transform.selectors'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('ProcessGroupManager', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/manager/processor-manager.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/processor-manager.service.spec.ts index 483c71c118..022eb13d46 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/processor-manager.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/processor-manager.service.spec.ts @@ -28,8 +28,8 @@ import { selectFlowState } from '../../state/flow/flow.selectors'; import { selectTransform } from '../../state/transform/transform.selectors'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('ProcessorManager', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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/manager/remote-process-group-manager.service.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/remote-process-group-manager.service.spec.ts index 2a885caf80..5ba295003a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/remote-process-group-manager.service.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/remote-process-group-manager.service.spec.ts @@ -28,8 +28,8 @@ import { selectFlowState } from '../../state/flow/flow.selectors'; import { selectTransform } from '../../state/transform/transform.selectors'; import { controllerServicesFeatureKey } from '../../state/controller-services'; import * as fromControllerServices from '../../state/controller-services/controller-services.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; -import * as fromUser from '../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../state/current-user/current-user.reducer'; import { parameterFeatureKey } from '../../state/parameter'; import * as fromParameter from '../../state/parameter/parameter.reducer'; @@ -58,7 +58,7 @@ describe('RemoteProcessGroupManager', () => { value: initialState[transformFeatureKey] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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 1cb77a8e70..81eb270f47 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 @@ -115,7 +115,11 @@ Node Status History - 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.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/header/header.component.spec.ts index fb47ed315d..faf85c4678 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.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/header/header.component.spec.ts @@ -36,8 +36,8 @@ import { ClusterSummary, ControllerStatus } from '../../../state/flow'; import { Search } from './search/search.component'; import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { selectUser } from '../../../../../state/user/user.selectors'; -import * as fromUser from '../../../../../state/user/user.reducer'; +import { selectCurrentUser } from '../../../../../state/current-user/current-user.selectors'; +import * as fromUser from '../../../../../state/current-user/current-user.reducer'; describe('HeaderComponent', () => { let component: HeaderComponent; @@ -103,7 +103,7 @@ describe('HeaderComponent', () => { value: [] }, { - selector: selectUser, + selector: selectCurrentUser, value: fromUser.initialState.user } ] 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.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/header/header.component.ts index 56f3f12a5a..27ab07c29e 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.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/header/header.component.ts @@ -26,8 +26,8 @@ import { selectCurrentProcessGroupId, selectLastRefreshed } from '../../../state/flow/flow.selectors'; -import { selectUser } from '../../../../../state/user/user.selectors'; -import { User } from '../../../../../state/user'; +import { selectCurrentUser } from '../../../../../state/current-user/current-user.selectors'; +import { CurrentUser } from '../../../../../state/current-user'; import { AuthStorage } from '../../../../../service/auth-storage.service'; import { AuthService } from '../../../../../service/auth.service'; import { LoadingService } from '../../../../../service/loading.service'; @@ -63,7 +63,7 @@ export class HeaderComponent { lastRefreshed$ = this.store.select(selectLastRefreshed); clusterSummary$ = this.store.select(selectClusterSummary); controllerBulletins$ = this.store.select(selectControllerBulletins); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); currentProcessGroupId$ = this.store.select(selectCurrentProcessGroupId); constructor( @@ -73,7 +73,7 @@ export class HeaderComponent { public loadingService: LoadingService ) {} - allowLogin(user: User): boolean { + allowLogin(user: CurrentUser): boolean { return user.anonymous && location.protocol === 'https:'; } 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/root/guard/root-group.guard.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.ts index 1c961acc41..f435146fd3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.ts @@ -20,7 +20,7 @@ import { FlowService } from '../../../service/flow.service'; import { inject } from '@angular/core'; import { switchMap, take } from 'rxjs'; import { Store } from '@ngrx/store'; -import { UserState } from '../../../../../state/user'; +import { CurrentUserState } from '../../../../../state/current-user'; import { FlowState } from '../../../state/flow'; import { selectCurrentProcessGroupId } from '../../../state/flow/flow.selectors'; import { initialState } from '../../../state/flow/flow.reducer'; @@ -28,7 +28,7 @@ import { initialState } from '../../../state/flow/flow.reducer'; export const rootGroupGuard: CanActivateFn = (route, state) => { const router: Router = inject(Router); const flowService: FlowService = inject(FlowService); - const store: Store = inject(Store); + const store: Store = inject(Store); return store.select(selectCurrentProcessGroupId).pipe( take(1), diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.spec.ts index a09bfd158c..b527199885 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.spec.ts @@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Login } from './login.component'; import { provideMockStore } from '@ngrx/store/testing'; -import { initialState } from '../../../state/user/user.reducer'; +import { initialState } from '../../../state/current-user/current-user.reducer'; describe('Login', () => { let component: Login; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.spec.ts index b0ae5a750b..47bd73293f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.spec.ts @@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { LoginForm } from './login-form.component'; import { provideMockStore } from '@ngrx/store/testing'; -import { initialState } from '../../../../state/user/user.reducer'; +import { initialState } from '../../../../state/current-user/current-user.reducer'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { MatFormFieldModule } from '@angular/material/form-field'; import { RouterModule } from '@angular/router'; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/feature/parameter-contexts.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/feature/parameter-contexts.component.ts index ff50209713..4afc041cd3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/feature/parameter-contexts.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/feature/parameter-contexts.component.ts @@ -18,7 +18,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { NiFiState } from '../../../state'; -import { startUserPolling, stopUserPolling } from '../../../state/user/user.actions'; +import { startCurrentUserPolling, stopCurrentUserPolling } from '../../../state/current-user/current-user.actions'; @Component({ selector: 'parameter-contexts', @@ -29,10 +29,10 @@ export class ParameterContexts implements OnInit, OnDestroy { constructor(private store: Store) {} ngOnInit(): void { - this.store.dispatch(startUserPolling()); + this.store.dispatch(startCurrentUserPolling()); } ngOnDestroy(): void { - this.store.dispatch(stopUserPolling()); + this.store.dispatch(stopCurrentUserPolling()); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/feature/provenance.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/feature/provenance.component.ts index 3ae2282b2c..1344a2ce4f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/feature/provenance.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/feature/provenance.component.ts @@ -18,7 +18,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { NiFiState } from '../../../state'; -import { startUserPolling, stopUserPolling } from '../../../state/user/user.actions'; +import { startCurrentUserPolling, stopCurrentUserPolling } from '../../../state/current-user/current-user.actions'; import { loadProvenanceOptions } from '../state/provenance-event-listing/provenance-event-listing.actions'; import { loadAbout } from '../../../state/about/about.actions'; @@ -31,12 +31,12 @@ export class Provenance implements OnInit, OnDestroy { constructor(private store: Store) {} ngOnInit(): void { - this.store.dispatch(startUserPolling()); + this.store.dispatch(startCurrentUserPolling()); this.store.dispatch(loadProvenanceOptions()); this.store.dispatch(loadAbout()); } ngOnDestroy(): void { - this.store.dispatch(stopUserPolling()); + this.store.dispatch(stopCurrentUserPolling()); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings-routing.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings-routing.module.ts index 01bdb86c47..9675c24b41 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings-routing.module.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings-routing.module.ts @@ -25,13 +25,13 @@ import { FlowAnalysisRules } from '../ui/flow-analysis-rules/flow-analysis-rules import { RegistryClients } from '../ui/registry-clients/registry-clients.component'; import { ParameterProviders } from '../ui/parameter-providers/parameter-providers.component'; import { authorizationGuard } from '../../../service/guard/authorization.guard'; -import { User } from '../../../state/user'; +import { CurrentUser } from '../../../state/current-user'; const routes: Routes = [ { path: '', component: Settings, - canMatch: [authorizationGuard((user: User) => user.controllerPermissions.canRead)], + canMatch: [authorizationGuard((user: CurrentUser) => user.controllerPermissions.canRead)], children: [ { path: '', pathMatch: 'full', redirectTo: 'general' }, { path: 'general', component: General }, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings.component.ts index c92917e579..ecacf6f38b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings.component.ts @@ -18,7 +18,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { NiFiState } from '../../../state'; -import { startUserPolling, stopUserPolling } from '../../../state/user/user.actions'; +import { startCurrentUserPolling, stopCurrentUserPolling } from '../../../state/current-user/current-user.actions'; import { loadExtensionTypesForSettings } from '../../../state/extension-types/extension-types.actions'; @Component({ @@ -57,11 +57,11 @@ export class Settings implements OnInit, OnDestroy { constructor(private store: Store) {} ngOnInit(): void { - this.store.dispatch(startUserPolling()); + this.store.dispatch(startCurrentUserPolling()); this.store.dispatch(loadExtensionTypesForSettings()); } ngOnDestroy(): void { - this.store.dispatch(stopUserPolling()); + this.store.dispatch(stopCurrentUserPolling()); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/general/general-form/general-form.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/general/general-form/general-form.component.ts index df0249858a..d6e2059996 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/general/general-form/general-form.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/general/general-form/general-form.component.ts @@ -21,7 +21,7 @@ import { ControllerEntity, GeneralState, UpdateControllerConfigRequest } from '. import { Store } from '@ngrx/store'; import { updateControllerConfig } from '../../../state/general/general.actions'; import { Client } from '../../../../../service/client.service'; -import { selectUser } from '../../../../../state/user/user.selectors'; +import { selectCurrentUser } from '../../../../../state/current-user/current-user.selectors'; @Component({ selector: 'general-form', @@ -36,7 +36,7 @@ export class GeneralForm { this.controllerForm.get('timerDrivenThreadCount')?.setValue(controller.component.maxTimerDrivenThreadCount); } - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); controllerForm: FormGroup; constructor( diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts index 4e9b2881ed..efccccc47e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts @@ -39,7 +39,7 @@ import { ControllerServiceEntity } from '../../../../state/shared'; import { initialState } from '../../state/management-controller-services/management-controller-services.reducer'; import { filter, switchMap, take } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { selectUser } from '../../../../state/user/user.selectors'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { NiFiState } from '../../../../state'; import { state } from '@angular/animations'; import { resetEnableControllerServiceState } from '../../../../state/contoller-service-state/controller-service-state.actions'; @@ -52,7 +52,7 @@ import { resetEnableControllerServiceState } from '../../../../state/contoller-s export class ManagementControllerServices implements OnInit, OnDestroy { serviceState$ = this.store.select(selectManagementControllerServicesState); selectedServiceId$ = this.store.select(selectControllerServiceIdFromRoute); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); constructor(private store: Store) { this.store 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 1599725951..7a4446e961 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 @@ -34,7 +34,7 @@ import { } 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 { 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'; @@ -48,7 +48,7 @@ import { state } from '@angular/animations'; export class RegistryClients implements OnInit, OnDestroy { registryClientsState$ = this.store.select(selectRegistryClientsState); selectedRegistryClientId$ = this.store.select(selectRegistryClientIdFromRoute); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); constructor(private store: Store) { this.store diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts index 2b22f4a37a..72781f764d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts @@ -32,7 +32,7 @@ import { stopReportingTask } from '../../state/reporting-tasks/reporting-tasks.actions'; import { initialState } from '../../state/reporting-tasks/reporting-tasks.reducer'; -import { selectUser } from '../../../../state/user/user.selectors'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { NiFiState } from '../../../../state'; import { state } from '@angular/animations'; @@ -44,7 +44,7 @@ import { state } from '@angular/animations'; export class ReportingTasks implements OnInit, OnDestroy { reportingTaskState$ = this.store.select(selectReportingTasksState); selectedReportingTaskId$ = this.store.select(selectReportingTaskIdFromRoute); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); constructor(private store: Store) {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/feature/summary.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/feature/summary.component.ts index bbdd57c73a..1d37d167c3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/feature/summary.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/feature/summary.component.ts @@ -18,7 +18,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { NiFiState } from '../../../state'; -import { startUserPolling, stopUserPolling } from '../../../state/user/user.actions'; +import { startCurrentUserPolling, stopCurrentUserPolling } from '../../../state/current-user/current-user.actions'; import { loadSummaryListing, resetSummaryState } from '../state/summary-listing/summary-listing.actions'; interface TabLink { @@ -44,12 +44,12 @@ export class Summary implements OnInit, OnDestroy { constructor(private store: Store) {} ngOnInit(): void { - this.store.dispatch(startUserPolling()); + this.store.dispatch(startCurrentUserPolling()); this.store.dispatch(loadSummaryListing({ recursive: true })); } ngOnDestroy(): void { this.store.dispatch(resetSummaryState()); - this.store.dispatch(stopUserPolling()); + this.store.dispatch(stopCurrentUserPolling()); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/connection-status-listing/connection-status-listing.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/connection-status-listing/connection-status-listing.component.ts index 43a97bed6b..1209253335 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/connection-status-listing/connection-status-listing.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/connection-status-listing/connection-status-listing.component.ts @@ -33,7 +33,7 @@ import { selectSummaryListingStatus, selectViewStatusHistory } from '../../state/summary-listing/summary-listing.selectors'; -import { selectUser } from '../../../../state/user/user.selectors'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { filter, switchMap, take } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { @@ -51,7 +51,7 @@ import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diag export class ConnectionStatusListing { loadedTimestamp$ = this.store.select(selectSummaryListingLoadedTimestamp); summaryListingStatus$ = this.store.select(selectSummaryListingStatus); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); connectionStatusSnapshots$ = this.store.select(selectConnectionStatusSnapshots); selectedConnectionId$ = this.store.select(selectConnectionIdFromRoute); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/input-port-status-listing/input-port-status-listing.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/input-port-status-listing/input-port-status-listing.component.ts index cf88db2a79..dc432c3d90 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/input-port-status-listing/input-port-status-listing.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/input-port-status-listing/input-port-status-listing.component.ts @@ -22,7 +22,7 @@ import { selectSummaryListingLoadedTimestamp, selectSummaryListingStatus } from '../../state/summary-listing/summary-listing.selectors'; -import { selectUser } from '../../../../state/user/user.selectors'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { PortStatusSnapshotEntity, SummaryListingState } from '../../state/summary-listing'; import { Store } from '@ngrx/store'; import { initialState } from '../../state/summary-listing/summary-listing.reducer'; @@ -38,7 +38,7 @@ export class InputPortStatusListing { portStatusSnapshots$ = this.store.select(selectInputPortStatusSnapshots); loadedTimestamp$ = this.store.select(selectSummaryListingLoadedTimestamp); summaryListingStatus$ = this.store.select(selectSummaryListingStatus); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); selectedPortId$ = this.store.select(selectInputPortIdFromRoute); constructor(private store: Store) {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/output-port-status-listing/output-port-status-listing.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/output-port-status-listing/output-port-status-listing.component.ts index 8a2383ee8f..b90d919015 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/output-port-status-listing/output-port-status-listing.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/output-port-status-listing/output-port-status-listing.component.ts @@ -24,7 +24,7 @@ import { selectSummaryListingLoadedTimestamp, selectSummaryListingStatus } from '../../state/summary-listing/summary-listing.selectors'; -import { selectUser } from '../../../../state/user/user.selectors'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { Store } from '@ngrx/store'; import { PortStatusSnapshotEntity, SummaryListingState } from '../../state/summary-listing'; import { initialState } from '../../state/summary-listing/summary-listing.reducer'; @@ -40,7 +40,7 @@ export class OutputPortStatusListing { portStatusSnapshots$ = this.store.select(selectOutputPortStatusSnapshots); loadedTimestamp$ = this.store.select(selectSummaryListingLoadedTimestamp); summaryListingStatus$ = this.store.select(selectSummaryListingStatus); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); selectedPortId$ = this.store.select(selectOutputPortIdFromRoute); constructor(private store: Store) {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/process-group-status-listing/process-group-status-listing.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/process-group-status-listing/process-group-status-listing.component.ts index 8da7d29b21..03776189cd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/process-group-status-listing/process-group-status-listing.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/process-group-status-listing/process-group-status-listing.component.ts @@ -42,7 +42,7 @@ import { openStatusHistoryDialog } from '../../../../state/status-history/status-history.actions'; import { ComponentType } from '../../../../state/shared'; -import { selectUser } from '../../../../state/user/user.selectors'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diagnostics/system-diagnostics.actions'; @Component({ @@ -54,7 +54,7 @@ export class ProcessGroupStatusListing { processGroupStatusSnapshots$ = this.store.select(selectProcessGroupStatusSnapshots); loadedTimestamp$ = this.store.select(selectSummaryListingLoadedTimestamp); summaryListingStatus$ = this.store.select(selectSummaryListingStatus); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); selectedProcessGroupId$ = this.store.select(selectProcessGroupIdFromRoute); processGroupStatus$ = this.store.select(selectProcessGroupStatus); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/processor-status-listing/processor-status-listing.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/processor-status-listing/processor-status-listing.component.ts index 1796eb120b..d5185c75d9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/processor-status-listing/processor-status-listing.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/processor-status-listing/processor-status-listing.component.ts @@ -26,7 +26,7 @@ import { selectViewStatusHistory } from '../../state/summary-listing/summary-listing.selectors'; import { ProcessorStatusSnapshotEntity, SummaryListingState } from '../../state/summary-listing'; -import { selectUser } from '../../../../state/user/user.selectors'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { initialState } from '../../state/summary-listing/summary-listing.reducer'; import { getStatusHistoryAndOpenDialog, @@ -49,7 +49,7 @@ export class ProcessorStatusListing { summaryListingStatus$ = this.store.select(selectSummaryListingStatus); selectedProcessorId$ = this.store.select(selectProcessorIdFromRoute); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); constructor(private store: Store) { this.store diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/remote-process-group-status-listing/remote-process-group-status-listing.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/remote-process-group-status-listing/remote-process-group-status-listing.component.ts index 0b3b0f9948..73b22538da 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/remote-process-group-status-listing/remote-process-group-status-listing.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/remote-process-group-status-listing/remote-process-group-status-listing.component.ts @@ -24,7 +24,7 @@ import { selectSummaryListingStatus, selectViewStatusHistory } from '../../state/summary-listing/summary-listing.selectors'; -import { selectUser } from '../../../../state/user/user.selectors'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { Store } from '@ngrx/store'; import { RemoteProcessGroupStatusSnapshotEntity, SummaryListingState } from '../../state/summary-listing'; import { filter, switchMap, take } from 'rxjs'; @@ -46,7 +46,7 @@ import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diag export class RemoteProcessGroupStatusListing { loadedTimestamp$ = this.store.select(selectSummaryListingLoadedTimestamp); summaryListingStatus$ = this.store.select(selectSummaryListingStatus); - currentUser$ = this.store.select(selectUser); + currentUser$ = this.store.select(selectCurrentUser); rpgStatusSnapshots$ = this.store.select(selectRemoteProcessGroupStatusSnapshots); selectedRpgId$ = this.store.select(selectRemoteProcessGroupIdFromRoute); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users-routing.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users-routing.module.ts new file mode 100644 index 0000000000..cb04991e1e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users-routing.module.ts @@ -0,0 +1,52 @@ +/* + * 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 { RouterModule, Routes } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { Users } from './users.component'; +import { authorizationGuard } from '../../../service/guard/authorization.guard'; +import { CurrentUser } from '../../../state/current-user'; + +const routes: Routes = [ + { + path: '', + component: Users, + canMatch: [authorizationGuard((user: CurrentUser) => user.tenantsPermissions.canRead)], + children: [ + { + path: ':id', + component: Users, + children: [ + { + path: 'edit', + component: Users + }, + { + path: 'policies', + component: Users + } + ] + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class UsersRoutingModule {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.html new file mode 100644 index 0000000000..c37530a5cf --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.html @@ -0,0 +1,28 @@ + + +
+
+

NiFi Users

+ +
+
+ +
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.scss new file mode 100644 index 0000000000..2afacb5079 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.scss @@ -0,0 +1,20 @@ +/*! + * 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. + */ + +.user-header { + color: #728e9b; +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.spec.ts new file mode 100644 index 0000000000..e50430ffc8 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.spec.ts @@ -0,0 +1,44 @@ +/* + * 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 { Users } from './users.component'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideMockStore } from '@ngrx/store/testing'; +import { RouterModule } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { UserListing } from '../ui/user-listing/user-listing.component'; +import { initialState } from '../state/user-listing/user-listing.reducer'; + +describe('Users', () => { + let component: Users; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [Users, UserListing], + imports: [RouterModule, RouterTestingModule], + providers: [provideMockStore({ initialState })] + }); + fixture = TestBed.createComponent(Users); + 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/users/feature/users.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.ts new file mode 100644 index 0000000000..582e319c84 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.component.ts @@ -0,0 +1,40 @@ +/* + * 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, OnDestroy, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { NiFiState } from '../../../state'; +import { startCurrentUserPolling, stopCurrentUserPolling } from '../../../state/current-user/current-user.actions'; +import { resetUsersState } from '../state/user-listing/user-listing.actions'; + +@Component({ + selector: 'users', + templateUrl: './users.component.html', + styleUrls: ['./users.component.scss'] +}) +export class Users implements OnInit, OnDestroy { + constructor(private store: Store) {} + + ngOnInit(): void { + this.store.dispatch(startCurrentUserPolling()); + } + + ngOnDestroy(): void { + this.store.dispatch(resetUsersState()); + this.store.dispatch(stopCurrentUserPolling()); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.module.ts new file mode 100644 index 0000000000..f6aa8908c7 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/feature/users.module.ts @@ -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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Users } from './users.component'; +import { UsersRoutingModule } from './users-routing.module'; +import { StoreModule } from '@ngrx/store'; +import { usersFeatureKey, reducers } from '../state'; +import { EffectsModule } from '@ngrx/effects'; +import { MatDialogModule } from '@angular/material/dialog'; +import { UserListingEffects } from '../state/user-listing/user-listing.effects'; +import { UserListingModule } from '../ui/user-listing/user-listing.module'; + +@NgModule({ + declarations: [Users], + exports: [Users], + imports: [ + CommonModule, + UsersRoutingModule, + StoreModule.forFeature(usersFeatureKey, reducers), + EffectsModule.forFeature(UserListingEffects), + MatDialogModule, + UserListingModule + ] +}) +export class UsersModule {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/service/users.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/service/users.service.ts new file mode 100644 index 0000000000..2b1f18e95f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/service/users.service.ts @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Client } from '../../../service/client.service'; +import { Observable } from 'rxjs'; +import { NiFiCommon } from '../../../service/nifi-common.service'; +import { UserEntity, UserGroupEntity } from '../../../state/shared'; +import { + CreateUserGroupRequest, + CreateUserRequest, + UpdateUserGroupRequest, + UpdateUserRequest +} from '../state/user-listing'; + +@Injectable({ providedIn: 'root' }) +export class UsersService { + 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 + ) {} + + getUsers(): Observable { + return this.httpClient.get(`${UsersService.API}/tenants/users`); + } + + getUserGroups(): Observable { + return this.httpClient.get(`${UsersService.API}/tenants/user-groups`); + } + + createUser(request: CreateUserRequest): Observable { + const payload: any = { + revision: request.revision, + component: request.userPayload + }; + return this.httpClient.post(`${UsersService.API}/tenants/users`, payload); + } + + createUserGroup(request: CreateUserGroupRequest): Observable { + const payload: any = { + revision: request.revision, + component: request.userGroupPayload + }; + return this.httpClient.post(`${UsersService.API}/tenants/user-groups`, payload); + } + + updateUser(request: UpdateUserRequest): Observable { + const payload: any = { + revision: request.revision, + component: { + id: request.id, + ...request.userPayload + } + }; + return this.httpClient.put(this.stripProtocol(request.uri), payload); + } + + updateUserGroup(request: UpdateUserGroupRequest): Observable { + const payload: any = { + revision: request.revision, + component: { + id: request.id, + ...request.userGroupPayload + } + }; + return this.httpClient.put(this.stripProtocol(request.uri), payload); + } + + deleteUser(user: UserEntity): Observable { + const revision: any = this.client.getRevision(user); + return this.httpClient.delete(this.stripProtocol(user.uri), { params: revision }); + } + + deleteUserGroup(userGroup: UserGroupEntity): Observable { + const revision: any = this.client.getRevision(userGroup); + return this.httpClient.delete(this.stripProtocol(userGroup.uri), { params: revision }); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/index.ts new file mode 100644 index 0000000000..6e8073c468 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/index.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 { Action, combineReducers, createFeatureSelector } from '@ngrx/store'; +import { userListingReducer } from './user-listing/user-listing.reducer'; +import { UserListingState } from './user-listing'; + +export const usersFeatureKey = 'users'; + +export interface UsersState { + [usersFeatureKey]: UserListingState; +} + +export function reducers(state: UsersState | undefined, action: Action) { + return combineReducers({ + [usersFeatureKey]: userListingReducer + })(state, action); +} + +export const selectUserState = createFeatureSelector(usersFeatureKey); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/index.ts new file mode 100644 index 0000000000..b88ea61c4f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/index.ts @@ -0,0 +1,121 @@ +/* + * 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 { AccessPolicySummaryEntity, Revision, UserEntity, UserGroupEntity } from '../../../../state/shared'; + +export interface SelectedTenant { + id: string; + user?: UserEntity; + userGroup?: UserGroupEntity; +} + +export interface LoadTenantsSuccess { + users: UserEntity[]; + userGroups: UserGroupEntity[]; + loadedTimestamp: string; +} + +export interface CreateUserRequest { + revision: Revision; + userPayload: any; + userGroupUpdate?: { + requestId: number; + userGroups: string[]; + }; +} + +export interface CreateUserResponse { + user: UserEntity; + userGroupUpdate?: { + requestId: number; + userGroups: string[]; + }; +} + +export interface CreateUserGroupRequest { + revision: Revision; + userGroupPayload: any; +} + +export interface CreateUserGroupResponse { + userGroup: UserGroupEntity; +} + +export interface UpdateUserRequest { + revision: Revision; + id: string; + uri: string; + userPayload: any; + userGroupUpdate?: { + requestId: number; + userGroupsAdded: string[]; + userGroupsRemoved: string[]; + }; +} + +export interface UpdateUserResponse { + user: UserEntity; + userGroupUpdate?: { + requestId: number; + userGroupsAdded: string[]; + userGroupsRemoved: string[]; + }; +} + +export interface UpdateUserGroupRequest { + requestId?: number; + revision: Revision; + id: string; + uri: string; + userGroupPayload: any; +} + +export interface UpdateUserGroupResponse { + requestId?: number; + userGroup: UserGroupEntity; +} + +export interface EditUserDialogRequest { + user: UserEntity; +} + +export interface EditUserGroupDialogRequest { + userGroup: UserGroupEntity; +} + +export interface DeleteUserRequest { + user: UserEntity; +} + +export interface DeleteUserGroupRequest { + userGroup: UserGroupEntity; +} + +export interface UserAccessPoliciesDialogRequest { + id: string; + identity: string; + accessPolicies: AccessPolicySummaryEntity[]; +} + +export interface UserListingState { + users: UserEntity[]; + userGroups: UserGroupEntity[]; + saving: boolean; + loadedTimestamp: string; + error: string | null; + status: 'pending' | 'loading' | 'error' | 'success'; +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.actions.ts new file mode 100644 index 0000000000..2fd3d53557 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.actions.ts @@ -0,0 +1,145 @@ +/* + * 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 { + CreateUserGroupRequest, + CreateUserGroupResponse, + CreateUserRequest, + CreateUserResponse, + DeleteUserGroupRequest, + DeleteUserRequest, + EditUserGroupDialogRequest, + EditUserDialogRequest, + LoadTenantsSuccess, + UserAccessPoliciesDialogRequest, + UpdateUserRequest, + UpdateUserResponse, + UpdateUserGroupRequest, + UpdateUserGroupResponse +} from './index'; + +const USER_PREFIX: string = '[User Listing]'; + +export const resetUsersState = createAction(`${USER_PREFIX} Reset Users State`); + +export const loadTenants = createAction(`${USER_PREFIX} Load Tenants`); + +export const loadTenantsSuccess = createAction( + `${USER_PREFIX} Load Tenants Success`, + props<{ response: LoadTenantsSuccess }>() +); + +export const usersApiError = createAction(`${USER_PREFIX} Users Api Error`, props<{ error: string }>()); + +export const openCreateTenantDialog = createAction(`${USER_PREFIX} Open Create Tenant Dialog`); + +export const createUser = createAction(`${USER_PREFIX} Create User`, props<{ request: CreateUserRequest }>()); + +export const createUserSuccess = createAction( + `${USER_PREFIX} Create User Success`, + props<{ + response: CreateUserResponse; + }>() +); + +export const createUserComplete = createAction( + `${USER_PREFIX} Create User Complete`, + props<{ + response: CreateUserResponse; + }>() +); + +export const createUserGroup = createAction( + `${USER_PREFIX} Create User Group`, + props<{ + request: CreateUserGroupRequest; + }>() +); + +export const createUserGroupSuccess = createAction( + `${USER_PREFIX} Create User Group Success`, + props<{ + response: CreateUserGroupResponse; + }>() +); + +export const updateUser = createAction(`${USER_PREFIX} Update User`, props<{ request: UpdateUserRequest }>()); + +export const updateUserSuccess = createAction( + `${USER_PREFIX} Update User Success`, + props<{ + response: UpdateUserResponse; + }>() +); + +export const updateUserComplete = createAction(`${USER_PREFIX} Update User Complete`); + +export const updateUserGroup = createAction( + `${USER_PREFIX} Update User Group`, + props<{ + request: UpdateUserGroupRequest; + }>() +); + +export const updateUserGroupSuccess = createAction( + `${USER_PREFIX} Update User Group Success`, + props<{ + response: UpdateUserGroupResponse; + }>() +); + +export const selectTenant = createAction(`${USER_PREFIX} Select Tenant`, props<{ id: string }>()); + +export const navigateToEditTenant = createAction(`${USER_PREFIX} Navigate To Edit Tenant`, props<{ id: string }>()); + +export const openConfigureUserDialog = createAction( + `${USER_PREFIX} Open Configure User Dialog`, + props<{ request: EditUserDialogRequest }>() +); + +export const openConfigureUserGroupDialog = createAction( + `${USER_PREFIX} Open Configure User Group Dialog`, + props<{ request: EditUserGroupDialogRequest }>() +); + +export const navigateToViewAccessPolicies = createAction( + `${USER_PREFIX} Navigate To View Access Policies`, + props<{ id: string }>() +); + +export const openUserAccessPoliciesDialog = createAction( + `${USER_PREFIX} Open User Access Policy Dialog`, + props<{ request: UserAccessPoliciesDialogRequest }>() +); + +export const promptDeleteUser = createAction( + `${USER_PREFIX} Prompt Delete User`, + props<{ request: DeleteUserRequest }>() +); + +export const deleteUser = createAction(`${USER_PREFIX} Delete User`, props<{ request: DeleteUserRequest }>()); + +export const promptDeleteUserGroup = createAction( + `${USER_PREFIX} Prompt Delete User Group`, + props<{ request: DeleteUserGroupRequest }>() +); + +export const deleteUserGroup = createAction( + `${USER_PREFIX} Delete User Group`, + props<{ request: DeleteUserGroupRequest }>() +); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts new file mode 100644 index 0000000000..a66c44cc10 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts @@ -0,0 +1,734 @@ +/* + * 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 { NiFiState } from '../../../../state'; +import { Store } from '@ngrx/store'; +import { Router } from '@angular/router'; +import * as UserListingActions from './user-listing.actions'; +import { + catchError, + combineLatest, + filter, + from, + map, + mergeMap, + of, + switchMap, + take, + takeUntil, + tap, + withLatestFrom +} from 'rxjs'; +import { MatDialog } from '@angular/material/dialog'; +import { UsersService } from '../../service/users.service'; +import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component'; +import { EditTenantDialog } from '../../../../ui/common/edit-tenant/edit-tenant-dialog.component'; +import { selectSaving, selectUserGroups, selectUsers } from './user-listing.selectors'; +import { EditTenantRequest, UserGroupEntity } from '../../../../state/shared'; +import { selectTenant } from './user-listing.actions'; +import { Client } from '../../../../service/client.service'; +import { NiFiCommon } from '../../../../service/nifi-common.service'; +import { UserAccessPolicies } from '../../ui/user-listing/user-access-policies/user-access-policies.component'; + +@Injectable() +export class UserListingEffects { + private requestId: number = 0; + + constructor( + private actions$: Actions, + private client: Client, + private nifiCommon: NiFiCommon, + private store: Store, + private router: Router, + private usersService: UsersService, + private dialog: MatDialog + ) {} + + loadTenants$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.loadTenants), + switchMap(() => + combineLatest([this.usersService.getUsers(), this.usersService.getUserGroups()]).pipe( + map(([usersResponse, userGroupsResponse]) => + UserListingActions.loadTenantsSuccess({ + response: { + users: usersResponse.users, + userGroups: userGroupsResponse.userGroups, + loadedTimestamp: usersResponse.generated + } + }) + ), + catchError((error) => + of( + UserListingActions.usersApiError({ + error: error.error + }) + ) + ) + ) + ) + ) + ); + + selectTenant$ = createEffect( + () => + this.actions$.pipe( + ofType(UserListingActions.selectTenant), + map((action) => action.id), + tap((id) => { + this.router.navigate(['/users', id]); + }) + ), + { dispatch: false } + ); + + openCreateTenantDialog$ = createEffect( + () => + this.actions$.pipe( + ofType(UserListingActions.openCreateTenantDialog), + withLatestFrom(this.store.select(selectUsers), this.store.select(selectUserGroups)), + tap(([action, existingUsers, existingUserGroups]) => { + const editTenantRequest: EditTenantRequest = { + existingUsers, + existingUserGroups + }; + const dialogReference = this.dialog.open(EditTenantDialog, { + data: editTenantRequest, + panelClass: 'medium-dialog' + }); + + dialogReference.componentInstance.saving$ = this.store.select(selectSaving); + + dialogReference.componentInstance.editTenant + .pipe(takeUntil(dialogReference.afterClosed())) + .subscribe((response) => { + if (response.user) { + this.store.dispatch( + UserListingActions.createUser({ + request: { + revision: response.revision, + userPayload: response.user.payload, + userGroupUpdate: { + requestId: this.requestId++, + userGroups: response.user.userGroupsAdded + } + } + }) + ); + } else if (response.userGroup) { + const users: any[] = response.userGroup.users.map((id: string) => { + return { id }; + }); + + this.store.dispatch( + UserListingActions.createUserGroup({ + request: { + revision: response.revision, + userGroupPayload: { + ...response.userGroup.payload, + users + } + } + }) + ); + } + }); + }) + ), + { dispatch: false } + ); + + createUser$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.createUser), + map((action) => action.request), + switchMap((request) => + from(this.usersService.createUser(request)).pipe( + map((response) => + UserListingActions.createUserSuccess({ + response: { + user: response, + userGroupUpdate: request.userGroupUpdate + } + }) + ), + catchError((error) => of(UserListingActions.usersApiError({ error: error.error }))) + ) + ) + ) + ); + + createUserSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.createUserSuccess), + map((action) => action.response), + withLatestFrom(this.store.select(selectUserGroups)), + switchMap(([response, userGroups]) => { + if (response.userGroupUpdate) { + const userGroupUpdate = response.userGroupUpdate; + const userGroupUpdates = []; + + if (!this.nifiCommon.isEmpty(userGroupUpdate.userGroups)) { + userGroupUpdates.push( + ...userGroupUpdate.userGroups + .map((userGroupId: string) => + userGroups.find((userGroup) => userGroup.id == userGroupId) + ) + .filter((userGroup) => userGroup != null) + .map((userGroup) => { + // @ts-ignore + const ug: UserGroupEntity = userGroup; + + const users: any[] = [ + ...ug.component.users.map((user) => { + return { + id: user.id + }; + }), + { id: response.user.id } + ]; + + return UserListingActions.updateUserGroup({ + request: { + requestId: userGroupUpdate.requestId, + id: ug.id, + uri: ug.uri, + revision: this.client.getRevision(userGroup), + userGroupPayload: { + ...ug.component, + users + } + } + }); + }) + ); + } + + if (userGroupUpdates.length === 0) { + return of(UserListingActions.createUserComplete({ response })); + } else { + return userGroupUpdates; + } + } else { + return of(UserListingActions.createUserComplete({ response })); + } + }) + ) + ); + + awaitUpdateUserGroupsForCreateUser$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.createUserSuccess), + map((action) => action.response), + filter((response) => response.userGroupUpdate != null), + mergeMap((createUserResponse) => + this.actions$.pipe( + ofType(UserListingActions.updateUserGroupSuccess), + filter( + (updateSuccess) => + // @ts-ignore + createUserResponse.userGroupUpdate.requestId === updateSuccess.response.requestId + ), + map((response) => UserListingActions.createUserComplete({ response: createUserResponse })) + ) + ) + ) + ); + + createUserComplete$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.createUserComplete), + map((action) => action.response), + tap((response) => { + this.dialog.closeAll(); + this.store.dispatch(selectTenant({ id: response.user.id })); + }), + switchMap(() => of(UserListingActions.loadTenants())) + ) + ); + + createUserGroup$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.createUserGroup), + map((action) => action.request), + switchMap((request) => + from(this.usersService.createUserGroup(request)).pipe( + map((response) => + UserListingActions.createUserGroupSuccess({ + response: { + userGroup: response + } + }) + ), + catchError((error) => of(UserListingActions.usersApiError({ error: error.error }))) + ) + ) + ) + ); + + createUserGroupSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.createUserGroupSuccess), + map((action) => action.response), + tap((response) => { + this.dialog.closeAll(); + this.store.dispatch(selectTenant({ id: response.userGroup.id })); + }), + switchMap(() => of(UserListingActions.loadTenants())) + ) + ); + + navigateToEditTenant$ = createEffect( + () => + this.actions$.pipe( + ofType(UserListingActions.navigateToEditTenant), + map((action) => action.id), + tap((id) => { + this.router.navigate(['/users', id, 'edit']); + }) + ), + { dispatch: false } + ); + + openConfigureUserDialog$ = createEffect( + () => + this.actions$.pipe( + ofType(UserListingActions.openConfigureUserDialog), + map((action) => action.request), + withLatestFrom(this.store.select(selectUsers), this.store.select(selectUserGroups)), + tap(([request, existingUsers, existingUserGroups]) => { + const editTenantRequest: EditTenantRequest = { + user: request.user, + existingUsers, + existingUserGroups + }; + const dialogReference = this.dialog.open(EditTenantDialog, { + data: editTenantRequest, + panelClass: 'medium-dialog' + }); + + dialogReference.componentInstance.saving$ = this.store.select(selectSaving); + + dialogReference.componentInstance.editTenant + .pipe(takeUntil(dialogReference.afterClosed())) + .subscribe((response) => { + if (response.user) { + const userGroupsAdded: string[] = response.user.userGroupsAdded; + const userGroupsRemoved: string[] = response.user.userGroupsRemoved; + + if ( + this.nifiCommon.isEmpty(userGroupsAdded) && + this.nifiCommon.isEmpty(userGroupsRemoved) + ) { + this.store.dispatch( + UserListingActions.updateUser({ + request: { + revision: response.revision, + id: request.user.id, + uri: request.user.uri, + userPayload: { + ...request.user.component, + ...response.user.payload + } + } + }) + ); + } else { + this.store.dispatch( + UserListingActions.updateUser({ + request: { + revision: response.revision, + id: request.user.id, + uri: request.user.uri, + userPayload: { + ...request.user.component, + ...response.user.payload + }, + userGroupUpdate: { + requestId: this.requestId++, + userGroupsAdded: response.user.userGroupsAdded, + userGroupsRemoved: response.user.userGroupsRemoved + } + } + }) + ); + } + } + }); + + dialogReference.afterClosed().subscribe((response) => { + this.store.dispatch( + selectTenant({ + id: request.user.id + }) + ); + }); + }) + ), + { dispatch: false } + ); + + updateUser$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.updateUser), + map((action) => action.request), + switchMap((request) => + from(this.usersService.updateUser(request)).pipe( + map((response) => + UserListingActions.updateUserSuccess({ + response: { + user: response, + userGroupUpdate: request.userGroupUpdate + } + }) + ), + catchError((error) => of(UserListingActions.usersApiError({ error: error.error }))) + ) + ) + ) + ); + + updateUserSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.updateUserSuccess), + map((action) => action.response), + withLatestFrom(this.store.select(selectUserGroups)), + switchMap(([response, userGroups]) => { + if (response.userGroupUpdate) { + const userGroupUpdate = response.userGroupUpdate; + const userGroupUpdates = []; + + if (!this.nifiCommon.isEmpty(userGroupUpdate.userGroupsAdded)) { + userGroupUpdates.push( + ...userGroupUpdate.userGroupsAdded + .map((userGroupId: string) => + userGroups.find((userGroup) => userGroup.id == userGroupId) + ) + .filter((userGroup) => userGroup != null) + .map((userGroup) => { + // @ts-ignore + const ug: UserGroupEntity = userGroup; + + const users: any[] = [ + ...ug.component.users.map((user) => { + return { + id: user.id + }; + }), + { id: response.user.id } + ]; + + return UserListingActions.updateUserGroup({ + request: { + requestId: userGroupUpdate.requestId, + revision: this.client.getRevision(userGroup), + id: ug.id, + uri: ug.uri, + userGroupPayload: { + ...ug.component, + users + } + } + }); + }) + ); + } + + if (!this.nifiCommon.isEmpty(userGroupUpdate.userGroupsRemoved)) { + userGroupUpdates.push( + ...userGroupUpdate.userGroupsRemoved + .map((userGroupId: string) => + userGroups.find((userGroup) => userGroup.id == userGroupId) + ) + .filter((userGroup) => userGroup != null) + .map((userGroup) => { + // @ts-ignore + const ug: UserGroupEntity = userGroup; + + const users: any[] = [ + ...ug.component.users + .filter((user) => user.id != response.user.id) + .map((user) => { + return { + id: user.id + }; + }) + ]; + + return UserListingActions.updateUserGroup({ + request: { + requestId: userGroupUpdate.requestId, + revision: this.client.getRevision(userGroup), + id: ug.id, + uri: ug.uri, + userGroupPayload: { + ...ug.component, + users + } + } + }); + }) + ); + } + + if (userGroupUpdates.length === 0) { + return of(UserListingActions.updateUserComplete()); + } else { + return userGroupUpdates; + } + } else { + return of(UserListingActions.updateUserComplete()); + } + }) + ) + ); + + awaitUpdateUserGroupsForUpdateUser$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.updateUserSuccess), + map((action) => action.response), + filter((response) => response.userGroupUpdate != null), + mergeMap((updateUserResponse) => + this.actions$.pipe( + ofType(UserListingActions.updateUserGroupSuccess), + filter( + (updateSuccess) => + // @ts-ignore + updateUserResponse.userGroupUpdate.requestId === updateSuccess.response.requestId + ), + map((response) => UserListingActions.updateUserComplete()) + ) + ) + ) + ); + + updateUserComplete$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.updateUserComplete), + tap(() => { + this.dialog.closeAll(); + }), + switchMap((request) => of(UserListingActions.loadTenants())) + ) + ); + + openConfigureUserGroupDialog$ = createEffect( + () => + this.actions$.pipe( + ofType(UserListingActions.openConfigureUserGroupDialog), + map((action) => action.request), + withLatestFrom(this.store.select(selectUsers), this.store.select(selectUserGroups)), + tap(([request, existingUsers, existingUserGroups]) => { + const editTenantRequest: EditTenantRequest = { + userGroup: request.userGroup, + existingUsers, + existingUserGroups + }; + const dialogReference = this.dialog.open(EditTenantDialog, { + data: editTenantRequest, + panelClass: 'medium-dialog' + }); + + dialogReference.componentInstance.saving$ = this.store.select(selectSaving); + + dialogReference.componentInstance.editTenant + .pipe(takeUntil(dialogReference.afterClosed())) + .subscribe((response) => { + if (response.userGroup) { + const users: any[] = response.userGroup.users.map((id: string) => { + return { id }; + }); + + this.store.dispatch( + UserListingActions.updateUserGroup({ + request: { + revision: response.revision, + id: response.userGroup.id, + uri: request.userGroup.uri, + userGroupPayload: { + ...request.userGroup.component, + ...response.userGroup.payload, + users + } + } + }) + ); + } + }); + + dialogReference.afterClosed().subscribe(() => { + this.store.dispatch( + selectTenant({ + id: request.userGroup.id + }) + ); + }); + }) + ), + { dispatch: false } + ); + + updateUserGroup$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.updateUserGroup), + map((action) => action.request), + switchMap((request) => + from(this.usersService.updateUserGroup(request)).pipe( + map((response) => + UserListingActions.updateUserGroupSuccess({ + response: { + requestId: request.requestId, + userGroup: response + } + }) + ), + catchError((error) => of(UserListingActions.usersApiError({ error: error.error }))) + ) + ) + ) + ); + + updateUserGroupSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.updateUserGroupSuccess), + map((action) => action.response), + filter((response) => response.requestId == null), + tap(() => { + this.dialog.closeAll(); + }), + switchMap((request) => of(UserListingActions.loadTenants())) + ) + ); + + navigateToViewAccessPolicies$ = createEffect( + () => + this.actions$.pipe( + ofType(UserListingActions.navigateToViewAccessPolicies), + map((action) => action.id), + tap((id) => { + this.router.navigate(['/users', id, 'policies']); + }) + ), + { dispatch: false } + ); + + openUserAccessPoliciesDialog = createEffect( + () => + this.actions$.pipe( + ofType(UserListingActions.openUserAccessPoliciesDialog), + map((action) => action.request), + tap((request) => { + this.dialog + .open(UserAccessPolicies, { + data: request, + panelClass: 'large-dialog' + }) + .afterClosed() + .subscribe((response) => { + if (response != 'ROUTED') { + this.store.dispatch( + selectTenant({ + id: request.id + }) + ); + } + }); + }) + ), + { dispatch: false } + ); + + promptDeleteUser$ = createEffect( + () => + this.actions$.pipe( + ofType(UserListingActions.promptDeleteUser), + map((action) => action.request), + tap((request) => { + const dialogReference = this.dialog.open(YesNoDialog, { + data: { + title: 'Delete User Account', + message: `Are you sure you want to delete the user account for '${request.user.component.identity}'?` + }, + panelClass: 'small-dialog' + }); + + dialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => { + this.store.dispatch( + UserListingActions.deleteUser({ + request + }) + ); + }); + }) + ), + { dispatch: false } + ); + + deleteUser$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.deleteUser), + map((action) => action.request), + switchMap((request) => + from(this.usersService.deleteUser(request.user)).pipe( + map((response) => UserListingActions.loadTenants()), + catchError((error) => of(UserListingActions.usersApiError({ error: error.error }))) + ) + ) + ) + ); + + promptDeleteUserGroup$ = createEffect( + () => + this.actions$.pipe( + ofType(UserListingActions.promptDeleteUserGroup), + map((action) => action.request), + tap((request) => { + const dialogReference = this.dialog.open(YesNoDialog, { + data: { + title: 'Delete User Account', + message: `Are you sure you want to delete the user group account for '${request.userGroup.component.identity}'?` + }, + panelClass: 'small-dialog' + }); + + dialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => { + this.store.dispatch( + UserListingActions.deleteUserGroup({ + request + }) + ); + }); + }) + ), + { dispatch: false } + ); + + deleteUserGroup$ = createEffect(() => + this.actions$.pipe( + ofType(UserListingActions.deleteUserGroup), + map((action) => action.request), + switchMap((request) => + from(this.usersService.deleteUserGroup(request.userGroup)).pipe( + map(() => UserListingActions.loadTenants()), + catchError((error) => of(UserListingActions.usersApiError({ error: error.error }))) + ) + ) + ) + ); +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.reducer.ts new file mode 100644 index 0000000000..11babfb804 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.reducer.ts @@ -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 { UserListingState } from './index'; +import { createReducer, on } from '@ngrx/store'; +import { + createUser, + createUserComplete, + createUserGroup, + createUserGroupSuccess, + loadTenants, + loadTenantsSuccess, + resetUsersState, + updateUser, + updateUserComplete, + updateUserGroup, + updateUserGroupSuccess +} from './user-listing.actions'; + +export const initialState: UserListingState = { + users: [], + userGroups: [], + saving: false, + loadedTimestamp: '', + error: null, + status: 'pending' +}; + +export const userListingReducer = createReducer( + initialState, + on(resetUsersState, (state) => ({ + ...initialState + })), + on(loadTenants, (state) => ({ + ...state, + status: 'loading' as const + })), + on(loadTenantsSuccess, (state, { response }) => ({ + ...state, + users: response.users, + userGroups: response.userGroups, + loadedTimestamp: response.loadedTimestamp, + error: null, + status: 'success' as const + })), + on(createUser, updateUser, createUserGroup, updateUserGroup, (state) => ({ + ...state, + saving: true + })), + on(createUserComplete, (state) => ({ + ...state, + saving: false + })), + on(updateUserComplete, (state) => ({ + ...state, + saving: false + })), + on(createUserGroupSuccess, (state, { response }) => ({ + ...state, + saving: false + })), + on(updateUserGroupSuccess, (state, { response }) => ({ + ...state, + saving: response.requestId == null ? false : state.saving + })) +); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts new file mode 100644 index 0000000000..632381dbbe --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts @@ -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. + */ + +import { createSelector } from '@ngrx/store'; +import { usersFeatureKey, UsersState, selectUserState } from '../index'; +import { SelectedTenant, UserListingState } from './index'; +import { selectCurrentRoute } from '../../../../state/router/router.selectors'; + +export const selectUserListingState = createSelector(selectUserState, (state: UsersState) => state[usersFeatureKey]); + +export const selectSaving = createSelector(selectUserListingState, (state: UserListingState) => state.saving); + +export const selectUsers = createSelector(selectUserListingState, (state: UserListingState) => state.users); + +export const selectUserGroups = createSelector(selectUserListingState, (state: UserListingState) => state.userGroups); + +export const selectTenantIdFromRoute = createSelector(selectCurrentRoute, (route) => { + if (route) { + return route.params.id; + } + return null; +}); + +export const selectSingleEditedTenant = createSelector(selectCurrentRoute, (route) => { + if (route?.routeConfig?.path == 'edit') { + return route.params.id; + } + return null; +}); + +export const selectTenantForAccessPolicies = createSelector(selectCurrentRoute, (route) => { + if (route?.routeConfig?.path == 'policies') { + return route.params.id; + } + return null; +}); + +export const selectSelectedTenant = (id: string) => + createSelector(selectUserListingState, (state: UserListingState) => { + const user = state.users.find((user) => id == user.id); + if (user) { + return { + id, + user + } as SelectedTenant; + } + const userGroup = state.userGroups.find((userGroup) => id == userGroup.id); + if (userGroup) { + return { + id, + userGroup + } as SelectedTenant; + } + return null; + }); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.html new file mode 100644 index 0000000000..29a5e4ee3c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.html @@ -0,0 +1,88 @@ + + +
+

User Policies

+ +
+
+
User
+
{{ request.identity }}
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Policy +
+ {{ formatPolicy(item) }} +
+ +
{{ item.id }}
+
+
Action + {{ item.component.action }} + +
+
+
+
+
+ Some policies may be inherited by descendant components unless explicitly overridden. +
+
+
+ + + +
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.scss new file mode 100644 index 0000000000..52b37a1d6a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.scss @@ -0,0 +1,43 @@ +/* + * 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; + +.user-access-policies { + @include mat.button-density(-1); + + font-size: 14px; + + .listing-table { + table { + width: auto; + + td, + th { + cursor: default; + } + + .mat-column-action { + width: 75px; + } + + .mat-column-actions { + width: 50px; + } + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts new file mode 100644 index 0000000000..23833e42c8 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts @@ -0,0 +1,66 @@ +/* + * 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 { UserAccessPolicies } from './user-access-policies.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { UserAccessPoliciesDialogRequest } from '../../../state/user-listing'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; + +describe('UserAccessPolicies', () => { + let component: UserAccessPolicies; + let fixture: ComponentFixture; + + const data: UserAccessPoliciesDialogRequest = { + id: 'acfbfa2c-018c-1000-0311-47b83e34c9c3', + identity: 'group 1', + accessPolicies: [ + { + revision: { + clientId: 'b09bd713-018c-1000-e5b8-14855e466f1b', + version: 4 + }, + id: 'b0c3148d-018c-1000-2cfe-8fab902c11f7', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'b0c3148d-018c-1000-2cfe-8fab902c11f7', + resource: '/system', + action: 'read', + configurable: true + } + } + ] + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [UserAccessPolicies, BrowserAnimationsModule], + providers: [{ provide: MAT_DIALOG_DATA, useValue: data }] + }); + fixture = TestBed.createComponent(UserAccessPolicies); + 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/users/ui/user-listing/user-access-policies/user-access-policies.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.ts new file mode 100644 index 0000000000..675cb10de3 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.ts @@ -0,0 +1,300 @@ +/* + * 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 { MatButtonModule } from '@angular/material/button'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { MatSortModule, Sort } from '@angular/material/sort'; +import { NgIf } from '@angular/common'; +import { + AccessPolicySummaryEntity, + ComponentReferenceEntity, + ComponentType, + SelectOption +} from '../../../../../state/shared'; +import { NiFiCommon } from '../../../../../service/nifi-common.service'; +import { RouterLink } from '@angular/router'; +import { UserAccessPoliciesDialogRequest } from '../../../state/user-listing'; + +@Component({ + selector: 'user-access-policies', + standalone: true, + templateUrl: './user-access-policies.component.html', + imports: [MatButtonModule, MatDialogModule, MatTableModule, MatSortModule, NgIf, RouterLink], + styleUrls: ['./user-access-policies.component.scss', '../../../../../../assets/styles/listing-table.scss'] +}) +export class UserAccessPolicies { + displayedColumns: string[] = ['policy', 'action', 'actions']; + dataSource: MatTableDataSource = new MatTableDataSource(); + selectedPolicyId: string | null = null; + + sort: Sort = { + active: 'policy', + direction: 'asc' + }; + + constructor( + @Inject(MAT_DIALOG_DATA) public request: UserAccessPoliciesDialogRequest, + private nifiCommon: NiFiCommon + ) { + this.dataSource.data = this.sortPolicies(request.accessPolicies, this.sort); + } + + updateSort(sort: Sort): void { + this.sort = sort; + this.dataSource.data = this.sortPolicies(this.dataSource.data, sort); + } + + sortPolicies(policies: AccessPolicySummaryEntity[], sort: Sort): AccessPolicySummaryEntity[] { + const data: AccessPolicySummaryEntity[] = policies.slice(); + return data.sort((a, b) => { + const isAsc = sort.direction === 'asc'; + + let retVal: number = 0; + if (a.permissions.canRead && b.permissions.canRead) { + switch (sort.active) { + case 'policy': + retVal = this.nifiCommon.compareString(this.formatPolicy(a), this.formatPolicy(b)); + break; + case 'action': + retVal = this.nifiCommon.compareString(a.component.action, b.component.action); + break; + } + } else { + if (!a.permissions.canRead && !b.permissions.canRead) { + retVal = 0; + } + if (a.permissions.canRead) { + retVal = 1; + } else { + retVal = -1; + } + } + + return retVal * (isAsc ? 1 : -1); + }); + } + + formatPolicy(policy: AccessPolicySummaryEntity): string { + if (policy.component.resource.startsWith('/restricted-components')) { + // restricted components policy + return this.restrictedComponentResourceParser(policy); + } else if (policy.component.componentReference) { + // not restricted/global policy... check if user has access to the component reference + return this.componentResourceParser(policy); + } else { + // may be a global policy + const policyValue: string = this.nifiCommon.substringAfterLast(policy.component.resource, '/'); + const policyOption: SelectOption | undefined = this.nifiCommon.getPolicyTypeListing(policyValue); + + // if known global policy, format it otherwise format as unknown + if (policyOption) { + return this.globalResourceParser(policyOption); + } else { + return this.unknownResourceParser(policy); + } + } + } + + /** + * Generates a human-readable restricted component policy string. + * + * @returns {string} + * @param policy + */ + private restrictedComponentResourceParser(policy: AccessPolicySummaryEntity): string { + const resource: string = policy.component.resource; + + if (resource === '/restricted-components') { + return 'Restricted components regardless of restrictions'; + } + + var subResource = this.nifiCommon.substringAfterFirst(resource, '/restricted-components/'); + return `Restricted components requiring '${subResource}'`; + } + + /** + * Generates a human-readable component policy string. + * + * @returns {string} + * @param policy + */ + private componentResourceParser(policy: AccessPolicySummaryEntity): string { + let resource: string = policy.component.resource; + let policyLabel: string = ''; + + // determine policy type + if (resource.startsWith('/policies')) { + resource = this.nifiCommon.substringAfterFirst(resource, '/policies'); + policyLabel += 'Admin policy for '; + } else if (resource.startsWith('/data-transfer')) { + resource = this.nifiCommon.substringAfterFirst(resource, '/data-transfer'); + policyLabel += 'Site to site policy for '; + } else if (resource.startsWith('/data')) { + resource = this.nifiCommon.substringAfterFirst(resource, '/data'); + policyLabel += 'Data policy for '; + } else if (resource.startsWith('/operation')) { + resource = this.nifiCommon.substringAfterFirst(resource, '/operation'); + policyLabel += 'Operate policy for '; + } else { + policyLabel += 'Component policy for '; + } + + if (resource.startsWith('/processors')) { + policyLabel += 'processor '; + } else if (resource.startsWith('/controller-services')) { + policyLabel += 'controller service '; + } else if (resource.startsWith('/funnels')) { + policyLabel += 'funnel '; + } else if (resource.startsWith('/input-ports')) { + policyLabel += 'input port '; + } else if (resource.startsWith('/labels')) { + policyLabel += 'label '; + } else if (resource.startsWith('/output-ports')) { + policyLabel += 'output port '; + } else if (resource.startsWith('/process-groups')) { + policyLabel += 'process group '; + } else if (resource.startsWith('/remote-process-groups')) { + policyLabel += 'remote process group '; + } else if (resource.startsWith('/reporting-tasks')) { + policyLabel += 'reporting task '; + } else if (resource.startsWith('/parameter-contexts')) { + policyLabel += 'parameter context '; + } + + const componentReference: ComponentReferenceEntity | undefined = policy.component.componentReference; + if (componentReference) { + if (componentReference.permissions.canRead) { + policyLabel += componentReference.component.name; + } else { + policyLabel += componentReference.id; + } + } + + return policyLabel; + } + + /** + * Generates a human-readable global policy string. + * + * @param policy + * @returns {string} + */ + globalResourceParser(policy: SelectOption): string { + return `Global policy to ${policy.text}`; + } + + /** + * Generates a human-readable policy string for an unknown resource. + * + * @returns {string} + * @param policy + */ + unknownResourceParser(policy: AccessPolicySummaryEntity): string { + return `Unknown resource ${policy.component.resource}`; + } + + canGoToPolicyTarget(policy: AccessPolicySummaryEntity): boolean { + return policy.permissions.canRead && policy.component.componentReference != null; + } + + getPolicyTargetLink(policy: AccessPolicySummaryEntity): string[] { + const resource: string = policy.component.resource; + + // @ts-ignore + const componentReference: ComponentReferenceEntity = policy.component.componentReference; + + if (resource.indexOf('/processors') >= 0) { + return [ + '/process-groups', + // @ts-ignore + componentReference.parentGroupId, + ComponentType.Processor, + componentReference.id + ]; + } else if (resource.indexOf('/controller-services') >= 0) { + if (componentReference.parentGroupId) { + return [ + '/process-groups', + componentReference.parentGroupId, + 'controller-services', + componentReference.id + ]; + } else { + return ['/settings', 'management-controller-services', componentReference.id]; + } + } else if (resource.indexOf('/funnels') >= 0) { + // @ts-ignore + return ['/process-groups', componentReference.parentGroupId, ComponentType.Funnel, componentReference.id]; + } else if (resource.indexOf('/input-ports') >= 0) { + return [ + '/process-groups', + // @ts-ignore + componentReference.parentGroupId, + ComponentType.InputPort, + componentReference.id + ]; + } else if (resource.indexOf('/labels') >= 0) { + // @ts-ignore + return ['/process-groups', componentReference.parentGroupId, ComponentType.Label, componentReference.id]; + } else if (resource.indexOf('/output-ports') >= 0) { + return [ + '/process-groups', + // @ts-ignore + componentReference.parentGroupId, + ComponentType.OutputPort, + componentReference.id + ]; + } else if (resource.indexOf('/process-groups') >= 0) { + if (componentReference.parentGroupId) { + return [ + '/process-groups', + componentReference.parentGroupId, + ComponentType.ProcessGroup, + componentReference.id + ]; + } else { + return ['/process-groups', componentReference.id]; + } + } else if (resource.indexOf('/remote-process-groups') >= 0) { + return [ + '/process-groups', + // @ts-ignore + componentReference.parentGroupId, + ComponentType.RemoteProcessGroup, + componentReference.id + ]; + } else if (resource.indexOf('/reporting-tasks') >= 0) { + return ['/settings', 'reporting-tasks', componentReference.id]; + } else if (resource.indexOf('/parameter-contexts') >= 0) { + return ['/parameter-contexts', componentReference.id]; + } + return ['/']; + } + + selectPolicy(policy: AccessPolicySummaryEntity): void { + this.selectedPolicyId = policy.id; + } + + isSelected(policy: AccessPolicySummaryEntity): boolean { + if (this.selectedPolicyId) { + return policy.id == this.selectedPolicyId; + } + return false; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.html new file mode 100644 index 0000000000..4065d6d145 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.html @@ -0,0 +1,49 @@ + + + +
+ +
+ + +
+
+ +
+
+
+ +
Last updated:
+
{{ userListingState.loadedTimestamp }}
+
+
+
+
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.scss new file mode 100644 index 0000000000..3651a8ab70 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.scss @@ -0,0 +1,16 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.spec.ts new file mode 100644 index 0000000000..1e1d0ebd5c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.spec.ts @@ -0,0 +1,40 @@ +/* + * 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 { UserListing } from './user-listing.component'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideMockStore } from '@ngrx/store/testing'; +import { initialState } from '../../state/user-listing/user-listing.reducer'; + +describe('UserListing', () => { + let component: UserListing; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [UserListing], + providers: [provideMockStore({ initialState })] + }); + fixture = TestBed.createComponent(UserListing); + 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/users/ui/user-listing/user-listing.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.ts new file mode 100644 index 0000000000..c71a0be3b6 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.component.ts @@ -0,0 +1,185 @@ +/* + * 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, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import { UserListingState } from '../../state/user-listing'; +import { + selectSelectedTenant, + selectSingleEditedTenant, + selectTenantForAccessPolicies, + selectTenantIdFromRoute, + selectUserListingState +} from '../../state/user-listing/user-listing.selectors'; +import { initialState } from '../../state/user-listing/user-listing.reducer'; +import { + openCreateTenantDialog, + loadTenants, + navigateToEditTenant, + navigateToViewAccessPolicies, + openConfigureUserDialog, + openConfigureUserGroupDialog, + openUserAccessPoliciesDialog, + promptDeleteUser, + promptDeleteUserGroup, + selectTenant +} from '../../state/user-listing/user-listing.actions'; +import { filter, switchMap, take } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { UserEntity, UserGroupEntity } from '../../../../state/shared'; + +@Component({ + selector: 'user-listing', + templateUrl: './user-listing.component.html', + styleUrls: ['./user-listing.component.scss'] +}) +export class UserListing implements OnInit { + userListingState$ = this.store.select(selectUserListingState); + selectedTenantId$ = this.store.select(selectTenantIdFromRoute); + currentUser$ = this.store.select(selectCurrentUser); + + constructor(private store: Store) { + this.store + .select(selectSingleEditedTenant) + .pipe( + filter((id: string) => id != null), + switchMap((id: string) => + this.store.select(selectSelectedTenant(id)).pipe( + filter((entity) => entity != null), + take(1) + ) + ), + takeUntilDestroyed() + ) + .subscribe((selectedTenant) => { + if (selectedTenant?.user) { + this.store.dispatch( + openConfigureUserDialog({ + request: { + user: selectedTenant.user + } + }) + ); + } else if (selectedTenant?.userGroup) { + this.store.dispatch( + openConfigureUserGroupDialog({ + request: { + userGroup: selectedTenant.userGroup + } + }) + ); + } + }); + + this.store + .select(selectTenantForAccessPolicies) + .pipe( + filter((id: string) => id != null), + switchMap((id: string) => + this.store.select(selectSelectedTenant(id)).pipe( + filter((entity) => entity != null), + take(1) + ) + ), + takeUntilDestroyed() + ) + .subscribe((selectedTenant) => { + if (selectedTenant?.user) { + this.store.dispatch( + openUserAccessPoliciesDialog({ + request: { + id: selectedTenant.user.id, + identity: selectedTenant.user.component.identity, + accessPolicies: selectedTenant.user.component.accessPolicies + } + }) + ); + } else if (selectedTenant?.userGroup) { + this.store.dispatch( + openUserAccessPoliciesDialog({ + request: { + id: selectedTenant.userGroup.id, + identity: selectedTenant.userGroup.component.identity, + accessPolicies: selectedTenant.userGroup.component.accessPolicies + } + }) + ); + } + }); + } + + ngOnInit(): void { + this.store.dispatch(loadTenants()); + } + + isInitialLoading(state: UserListingState): boolean { + return state.loadedTimestamp == initialState.loadedTimestamp; + } + + createTenant(): void { + this.store.dispatch(openCreateTenantDialog()); + } + + selectTenant(id: string): void { + this.store.dispatch( + selectTenant({ + id + }) + ); + } + + editTenant(id: string): void { + this.store.dispatch( + navigateToEditTenant({ + id + }) + ); + } + + deleteUser(user: UserEntity): void { + this.store.dispatch( + promptDeleteUser({ + request: { + user + } + }) + ); + } + + deleteUserGroup(userGroup: UserGroupEntity): void { + this.store.dispatch( + promptDeleteUserGroup({ + request: { + userGroup + } + }) + ); + } + + viewAccessPolicies(id: string): void { + this.store.dispatch( + navigateToViewAccessPolicies({ + id + }) + ); + } + + refreshUserListing() { + this.store.dispatch(loadTenants()); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.module.ts new file mode 100644 index 0000000000..6b15865cb8 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-listing.module.ts @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NgModule } from '@angular/core'; +import { UserListing } from './user-listing.component'; +import { CommonModule } from '@angular/common'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { MatTableModule } from '@angular/material/table'; +import { MatSortModule } from '@angular/material/sort'; +import { MatInputModule } from '@angular/material/input'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatSelectModule } from '@angular/material/select'; +import { UserTable } from './user-table/user-table.component'; + +@NgModule({ + declarations: [UserListing, UserTable], + exports: [UserListing], + imports: [ + CommonModule, + NgxSkeletonLoaderModule, + MatTableModule, + MatSortModule, + MatInputModule, + ReactiveFormsModule, + MatSelectModule + ] +}) +export class UserListingModule {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.html new file mode 100644 index 0000000000..349aca50e5 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.html @@ -0,0 +1,106 @@ + +
+
+
+
Displaying {{ filteredCount }} of {{ totalCount }}
+
+
+
+ + Filter + + +
+
+ + Filter By + + user + membership + + +
+
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
User + {{ item.user }} + Membership + {{ item.tenantType === 'user' ? 'Member of' : 'Members' }}: {{ formatMembership(item) }} + +
+
+
+
+
+
+
+
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.scss new file mode 100644 index 0000000000..181160a67c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.scss @@ -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. + */ + +.user-table { + .listing-table { + .mat-column-actions { + width: 75px; + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts new file mode 100644 index 0000000000..880018232e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts @@ -0,0 +1,194 @@ +/* + * 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 { UserTable } from './user-table.component'; +import { MatTableModule } from '@angular/material/table'; +import { MatSortModule } from '@angular/material/sort'; +import { MatInputModule } from '@angular/material/input'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatSelectModule } from '@angular/material/select'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { CurrentUser } from '../../../../../state/current-user'; + +describe('UserTable', () => { + let component: UserTable; + let fixture: ComponentFixture; + + const currentUser: CurrentUser = { + identity: 'admin', + anonymous: false, + provenancePermissions: { + canRead: false, + canWrite: false + }, + countersPermissions: { + canRead: false, + canWrite: false + }, + tenantsPermissions: { + canRead: true, + canWrite: true + }, + controllerPermissions: { + canRead: true, + canWrite: true + }, + policiesPermissions: { + canRead: true, + canWrite: true + }, + systemPermissions: { + canRead: true, + canWrite: false + }, + parameterContextPermissions: { + canRead: true, + canWrite: true + }, + restrictedComponentsPermissions: { + canRead: false, + canWrite: true + }, + componentRestrictionPermissions: [ + { + requiredPermission: { + id: 'read-distributed-filesystem', + label: 'read distributed filesystem' + }, + permissions: { + canRead: false, + canWrite: true + } + }, + { + requiredPermission: { + id: 'access-keytab', + label: 'access keytab' + }, + permissions: { + canRead: false, + canWrite: true + } + }, + { + requiredPermission: { + id: 'export-nifi-details', + label: 'export nifi details' + }, + permissions: { + canRead: false, + canWrite: true + } + }, + { + requiredPermission: { + id: 'read-filesystem', + label: 'read filesystem' + }, + permissions: { + canRead: false, + canWrite: true + } + }, + { + requiredPermission: { + id: 'access-environment-credentials', + label: 'access environment credentials' + }, + permissions: { + canRead: false, + canWrite: true + } + }, + { + requiredPermission: { + id: 'reference-remote-resources', + label: 'reference remote resources' + }, + permissions: { + canRead: false, + canWrite: true + } + }, + { + requiredPermission: { + id: 'execute-code', + label: 'execute code' + }, + permissions: { + canRead: false, + canWrite: true + } + }, + { + requiredPermission: { + id: 'access-ticket-cache', + label: 'access ticket cache' + }, + permissions: { + canRead: false, + canWrite: true + } + }, + { + requiredPermission: { + id: 'write-filesystem', + label: 'write filesystem' + }, + permissions: { + canRead: false, + canWrite: true + } + }, + { + requiredPermission: { + id: 'write-distributed-filesystem', + label: 'write distributed filesystem' + }, + permissions: { + canRead: false, + canWrite: true + } + } + ], + canVersionFlows: false + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [UserTable], + imports: [ + MatTableModule, + MatSortModule, + MatInputModule, + ReactiveFormsModule, + MatSelectModule, + NoopAnimationsModule + ] + }); + fixture = TestBed.createComponent(UserTable); + component = fixture.componentInstance; + component.currentUser = currentUser; + 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/users/ui/user-listing/user-table/user-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.ts new file mode 100644 index 0000000000..608844140d --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.ts @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; +import { Sort } from '@angular/material/sort'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { debounceTime } from 'rxjs'; +import { NiFiCommon } from '../../../../../service/nifi-common.service'; +import { CurrentUser } from '../../../../../state/current-user'; +import { AccessPolicySummaryEntity, UserEntity, UserGroupEntity } from '../../../../../state/shared'; + +export interface TenantItem { + id: string; + user: string; + tenantType: 'user' | 'userGroup'; + membership: string[]; + configurable: boolean; +} + +export interface Tenants { + users: UserEntity[]; + userGroups: UserGroupEntity[]; +} + +@Component({ + selector: 'user-table', + templateUrl: './user-table.component.html', + styleUrls: ['./user-table.component.scss', '../../../../../../assets/styles/listing-table.scss'] +}) +export class UserTable implements AfterViewInit { + filterTerm: string = ''; + filterColumn: 'user' | 'membership' = 'user'; + totalCount: number = 0; + filteredCount: number = 0; + + displayedColumns: string[] = ['user', 'membership', 'actions']; + dataSource: MatTableDataSource = new MatTableDataSource(); + filterForm: FormGroup; + + userLookup: Map = new Map(); + userGroupLookup: Map = new Map(); + + @Input() set tenants(tenants: Tenants) { + this.userLookup.clear(); + this.userGroupLookup.clear(); + + const tenantItems: TenantItem[] = []; + tenants.users.forEach((user) => { + this.userLookup.set(user.id, user); + tenantItems.push({ + id: user.id, + tenantType: 'user', + user: user.component.identity, + membership: user.component.userGroups.map((userGroup) => userGroup.component.identity), + configurable: user.component.configurable + }); + }); + tenants.userGroups.forEach((userGroup) => { + this.userGroupLookup.set(userGroup.id, userGroup); + tenantItems.push({ + id: userGroup.id, + tenantType: 'userGroup', + user: userGroup.component.identity, + membership: userGroup.component.users.map((user) => user.component.identity), + configurable: userGroup.component.configurable + }); + }); + + this.dataSource.data = this.sortUsers(tenantItems, this.sort); + this.dataSource.filterPredicate = (data: TenantItem, filter: string) => { + const { filterTerm, filterColumn } = JSON.parse(filter); + if (filterColumn === 'user') { + return this.nifiCommon.stringContains(data.user, filterTerm, true); + } else { + return this.nifiCommon.stringContains(this.formatMembership(data), filterTerm, true); + } + }; + this.totalCount = tenantItems.length; + this.filteredCount = tenantItems.length; + + // apply any filtering to the new data + const filterTerm = this.filterForm.get('filterTerm')?.value; + if (filterTerm?.length > 0) { + const filterColumn = this.filterForm.get('filterColumn')?.value; + this.applyFilter(filterTerm, filterColumn); + } + } + + @Input() selectedTenantId!: string; + @Input() currentUser!: CurrentUser; + @Input() configurableUsersAndGroups!: boolean; + + @Output() createTenant: EventEmitter = new EventEmitter(); + @Output() selectTenant: EventEmitter = new EventEmitter(); + @Output() editTenant: EventEmitter = new EventEmitter(); + @Output() deleteUser: EventEmitter = new EventEmitter(); + @Output() deleteUserGroup: EventEmitter = new EventEmitter(); + @Output() viewAccessPolicies: EventEmitter = new EventEmitter(); + + sort: Sort = { + active: 'user', + direction: 'asc' + }; + + constructor( + private formBuilder: FormBuilder, + private nifiCommon: NiFiCommon + ) { + this.filterForm = this.formBuilder.group({ filterTerm: '', filterColumn: 'user' }); + } + + ngAfterViewInit(): void { + this.filterForm + .get('filterTerm') + ?.valueChanges.pipe(debounceTime(500)) + .subscribe((filterTerm: string) => { + const filterColumn = this.filterForm.get('filterColumn')?.value; + this.applyFilter(filterTerm, filterColumn); + }); + + this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => { + const filterTerm = this.filterForm.get('filterTerm')?.value; + this.applyFilter(filterTerm, filterColumn); + }); + } + + applyFilter(filterTerm: string, filterColumn: string) { + this.dataSource.filter = JSON.stringify({ filterTerm, filterColumn }); + this.filteredCount = this.dataSource.filteredData.length; + } + + updateSort(sort: Sort): void { + this.sort = sort; + this.dataSource.data = this.sortUsers(this.dataSource.data, sort); + } + + formatMembership(item: TenantItem): string { + return item.membership.sort((a, b) => this.nifiCommon.compareString(a, b)).join(', '); + } + + sortUsers(items: TenantItem[], sort: Sort): TenantItem[] { + const data: TenantItem[] = items.slice(); + return data.sort((a, b) => { + const isAsc = sort.direction === 'asc'; + + let retVal: number = 0; + switch (sort.active) { + case 'user': + retVal = this.nifiCommon.compareString(a.user, b.user); + break; + case 'membership': + retVal = this.nifiCommon.compareString(this.formatMembership(a), this.formatMembership(b)); + break; + } + + return retVal * (isAsc ? 1 : -1); + }); + } + + select(item: TenantItem): void { + this.selectTenant.next(item.id); + } + + isSelected(item: TenantItem): boolean { + if (this.selectedTenantId) { + return item.id == this.selectedTenantId; + } + return false; + } + + canModifyTenants(currentUser: CurrentUser): boolean { + return ( + currentUser.tenantsPermissions.canRead && + currentUser.tenantsPermissions.canWrite && + this.configurableUsersAndGroups + ); + } + + createClicked(): void { + this.createTenant.next(); + } + + canEditOrDelete(currentUser: CurrentUser, item: TenantItem): boolean { + return this.canModifyTenants(currentUser) && item.configurable; + } + + editClicked(item: TenantItem, event: MouseEvent): void { + event.stopPropagation(); + this.editTenant.next(item.id); + } + + deleteClicked(item: TenantItem): void { + if (item.tenantType === 'user') { + const user: UserEntity | undefined = this.userLookup.get(item.id); + if (user) { + this.deleteUser.next(user); + } + } else if (item.tenantType === 'userGroup') { + const userGroup: UserGroupEntity | undefined = this.userGroupLookup.get(item.id); + if (userGroup) { + this.deleteUserGroup.next(userGroup); + } + } + } + + private getAccessPolicies(item: TenantItem): AccessPolicySummaryEntity[] { + const accessPolicies: AccessPolicySummaryEntity[] = []; + if (item.tenantType === 'user') { + const user: UserEntity | undefined = this.userLookup.get(item.id); + if (user) { + accessPolicies.push(...user.component.accessPolicies); + } + } else if (item.tenantType === 'userGroup') { + const userGroup: UserGroupEntity | undefined = this.userGroupLookup.get(item.id); + if (userGroup) { + accessPolicies.push(...userGroup.component.accessPolicies); + } + } + return accessPolicies; + } + + hasAccessPolicies(item: TenantItem): boolean { + return !this.nifiCommon.isEmpty(this.getAccessPolicies(item)); + } + + viewAccessPoliciesClicked(item: TenantItem, event: MouseEvent): void { + event.stopPropagation(); + this.viewAccessPolicies.next(item.id); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/user.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/current-user.service.ts similarity index 90% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/user.service.ts rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/current-user.service.ts index a49c12dc3e..8415517810 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/user.service.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/current-user.service.ts @@ -20,12 +20,12 @@ import { Observable, throwError } from 'rxjs'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) -export class UserService { +export class CurrentUserService { private static readonly API: string = '../nifi-api'; constructor(private httpClient: HttpClient) {} getUser(): Observable { - return this.httpClient.get(`${UserService.API}/flow/current-user`); + return this.httpClient.get(`${CurrentUserService.API}/flow/current-user`); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts index 4ca051f4e1..7cf5227f6c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts @@ -20,17 +20,17 @@ import { inject } from '@angular/core'; import { AuthService } from '../auth.service'; import { AuthStorage } from '../auth-storage.service'; import { take } from 'rxjs'; -import { UserService } from '../user.service'; +import { CurrentUserService } from '../current-user.service'; import { Store } from '@ngrx/store'; -import { UserState } from '../../state/user'; -import { loadUserSuccess } from '../../state/user/user.actions'; -import { selectUserState } from '../../state/user/user.selectors'; +import { CurrentUserState } from '../../state/current-user'; +import { loadCurrentUserSuccess } from '../../state/current-user/current-user.actions'; +import { selectCurrentUserState } from '../../state/current-user/current-user.selectors'; export const authenticationGuard: CanMatchFn = (route, state) => { const authStorage: AuthStorage = inject(AuthStorage); const authService: AuthService = inject(AuthService); - const userService: UserService = inject(UserService); - const store: Store = inject(Store); + const userService: CurrentUserService = inject(CurrentUserService); + const store: Store = inject(Store); const handleAuthentication: Promise = new Promise((resolve) => { if (authStorage.hasToken()) { @@ -77,7 +77,7 @@ export const authenticationGuard: CanMatchFn = (route, state) => { return new Promise((resolve) => { handleAuthentication.finally(() => { store - .select(selectUserState) + .select(selectCurrentUserState) .pipe(take(1)) .subscribe((userState) => { if (userState.status == 'pending') { @@ -88,7 +88,7 @@ export const authenticationGuard: CanMatchFn = (route, state) => { next: (response) => { // store the loaded user store.dispatch( - loadUserSuccess({ + loadCurrentUserSuccess({ response: { user: response } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authorization.guard.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authorization.guard.ts index bc9db239a5..5c974dcdbc 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authorization.guard.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authorization.guard.ts @@ -19,15 +19,15 @@ 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'; +import { CurrentUser, CurrentUserState } from '../../state/current-user'; +import { selectCurrentUser } from '../../state/current-user/current-user.selectors'; -export const authorizationGuard = (authorizationCheck: (user: User) => boolean): CanMatchFn => { +export const authorizationGuard = (authorizationCheck: (user: CurrentUser) => boolean): CanMatchFn => { return (route: Route, state: UrlSegment[]) => { const router: Router = inject(Router); - const store: Store = inject(Store); + const store: Store = inject(Store); - return store.select(selectUser).pipe( + return store.select(selectCurrentUser).pipe( map((currentUser) => { if (authorizationCheck(currentUser)) { return true; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.spec.ts index 6c6326bc63..0d496d452f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.spec.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.spec.ts @@ -19,7 +19,7 @@ import { TestBed } from '@angular/core/testing'; import { PollingInterceptor } from './polling.interceptor'; import { provideMockStore } from '@ngrx/store/testing'; -import { initialState } from '../../state/user/user.reducer'; +import { initialState } from '../../state/current-user/current-user.reducer'; describe('PollingInterceptor', () => { let service: PollingInterceptor; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.ts index 15589d350b..f56b5110d0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.ts @@ -20,7 +20,7 @@ import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest import { Observable, tap } from 'rxjs'; import { NiFiState } from '../../state'; import { Store } from '@ngrx/store'; -import { stopUserPolling } from '../../state/user/user.actions'; +import { stopCurrentUserPolling } from '../../state/current-user/current-user.actions'; import { stopProcessGroupPolling } from '../../pages/flow-designer/state/flow/flow.actions'; @Injectable({ @@ -34,7 +34,7 @@ export class PollingInterceptor implements HttpInterceptor { tap({ error: (error) => { if (error instanceof HttpErrorResponse && error.status === 0) { - this.store.dispatch(stopUserPolling()); + this.store.dispatch(stopCurrentUserPolling()); this.store.dispatch(stopProcessGroupPolling()); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/nifi-common.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/nifi-common.service.ts index e550076063..b25f8fbc26 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/nifi-common.service.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/nifi-common.service.ts @@ -16,6 +16,7 @@ */ import { Injectable } from '@angular/core'; +import { SelectOption } from '../state/shared'; @Injectable({ providedIn: 'root' @@ -37,6 +38,65 @@ export class NiFiCommon { public static readonly BYTES_IN_GIGABYTE: number = 1073741824; public static readonly BYTES_IN_TERABYTE: number = 1099511627776; + private policyTypeListing: SelectOption[] = [ + { + text: 'view the user interface', + value: 'flow', + description: 'Allows users to view the user interface' + }, + { + text: 'access the controller', + value: 'controller', + description: + 'Allows users to view/modify the controller including Management Controller Services, Reporting Tasks, Registry Clients, Parameter Providers and nodes in the cluster' + }, + { + text: 'access parameter contexts', + value: 'parameter-contexts', + description: 'Allows users to view/modify Parameter Contexts' + }, + { + text: 'query provenance', + value: 'provenance', + description: 'Allows users to submit a Provenance Search and request Event Lineage' + }, + { + text: 'access restricted components', + value: 'restricted-components', + description: 'Allows users to create/modify restricted components assuming other permissions are sufficient' + }, + { + text: 'access all policies', + value: 'policies', + description: 'Allows users to view/modify the policies for all components' + }, + { + text: 'access users/user groups', + value: 'tenants', + description: 'Allows users to view/modify the users and user groups' + }, + { + text: 'retrieve site-to-site details', + value: 'site-to-site', + description: 'Allows other NiFi instances to retrieve Site-To-Site details of this NiFi' + }, + { + text: 'view system diagnostics', + value: 'system', + description: 'Allows users to view System Diagnostics' + }, + { + text: 'proxy user requests', + value: 'proxy', + description: 'Allows proxy machines to send requests on the behalf of others' + }, + { + text: 'access counters', + value: 'counters', + description: 'Allows users to view/modify Counters' + } + ]; + constructor() {} /** @@ -435,4 +495,13 @@ export class NiFiCommon { const locale: string = (navigator && navigator.language) || 'en'; return f.toLocaleString(locale, { maximumFractionDigits: 2, minimumFractionDigits: 2 }); } + + /** + * Gets the policy type for the specified resource. + * + * @param value + */ + public getPolicyTypeListing(value: string): SelectOption | undefined { + return this.policyTypeListing.find((policy: SelectOption) => value === policy.value); + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.actions.ts similarity index 55% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.actions.ts rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.actions.ts index e3df917d44..b71f93ae97 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.actions.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.actions.ts @@ -16,17 +16,19 @@ */ import { createAction, props } from '@ngrx/store'; -import { LoadUserResponse, UserState } from './index'; -import { LoadProcessGroupRequest, LoadProcessGroupResponse } from '../../pages/flow-designer/state/flow'; +import { LoadCurrentUserResponse } from './index'; -export const loadUser = createAction('[User] Load User'); +export const loadCurrentUser = createAction('[Current User] Load Current User'); -export const loadUserSuccess = createAction('[User] Load User Success', props<{ response: LoadUserResponse }>()); +export const loadCurrentUserSuccess = createAction( + '[Current User] Load Current User Success', + props<{ response: LoadCurrentUserResponse }>() +); -export const userApiError = createAction('[User] User Api Error', props<{ error: string }>()); +export const currentUserApiError = createAction('[Current User] Current User Api Error', props<{ error: string }>()); -export const clearUserApiError = createAction('[User] Clear User Api Error'); +export const clearCurrentUserApiError = createAction('[Current User] Clear Current User Api Error'); -export const startUserPolling = createAction('[User] Start User Polling'); +export const startCurrentUserPolling = createAction('[Current User] Start Current User Polling'); -export const stopUserPolling = createAction('[User] Stop User Polling'); +export const stopCurrentUserPolling = createAction('[Current User] Stop Current User Polling'); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.effects.ts similarity index 66% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.effects.ts rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.effects.ts index 55b9f0205a..038d63fa9d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.effects.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.effects.ts @@ -17,44 +17,46 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import * as UserActions from './user.actions'; +import * as UserActions from './current-user.actions'; import { asyncScheduler, catchError, from, interval, map, of, switchMap, takeUntil } from 'rxjs'; -import { UserService } from '../../service/user.service'; +import { CurrentUserService } from '../../service/current-user.service'; @Injectable() -export class UserEffects { +export class CurrentUserEffects { constructor( private actions$: Actions, - private userService: UserService + private userService: CurrentUserService ) {} - loadUser$ = createEffect(() => + loadCurrentUser$ = createEffect(() => this.actions$.pipe( - ofType(UserActions.loadUser), + ofType(UserActions.loadCurrentUser), switchMap(() => { return from( this.userService.getUser().pipe( map((response) => - UserActions.loadUserSuccess({ + UserActions.loadCurrentUserSuccess({ response: { user: response } }) ), - catchError((error) => of(UserActions.userApiError({ error: error.error }))) + catchError((error) => of(UserActions.currentUserApiError({ error: error.error }))) ) ); }) ) ); - startUserPolling$ = createEffect(() => + startCurrentUserPolling$ = createEffect(() => this.actions$.pipe( - ofType(UserActions.startUserPolling), + ofType(UserActions.startCurrentUserPolling), switchMap(() => - interval(30000, asyncScheduler).pipe(takeUntil(this.actions$.pipe(ofType(UserActions.stopUserPolling)))) + interval(30000, asyncScheduler).pipe( + takeUntil(this.actions$.pipe(ofType(UserActions.stopCurrentUserPolling))) + ) ), - switchMap(() => of(UserActions.loadUser())) + switchMap(() => of(UserActions.loadCurrentUser())) ) ); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.reducer.ts similarity index 79% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.reducer.ts rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.reducer.ts index 05e3345339..ed47a48d0f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.reducer.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.reducer.ts @@ -16,16 +16,21 @@ */ import { createReducer, on } from '@ngrx/store'; -import { UserState } from './index'; +import { CurrentUserState } from './index'; import { Permissions } from '../shared'; -import { clearUserApiError, loadUser, loadUserSuccess, userApiError } from './user.actions'; +import { + clearCurrentUserApiError, + loadCurrentUser, + loadCurrentUserSuccess, + currentUserApiError +} from './current-user.actions'; export const NO_PERMISSIONS: Permissions = { canRead: false, canWrite: false }; -export const initialState: UserState = { +export const initialState: CurrentUserState = { user: { identity: '', anonymous: true, @@ -44,24 +49,24 @@ export const initialState: UserState = { status: 'pending' }; -export const userReducer = createReducer( +export const currentUserReducer = createReducer( initialState, - on(loadUser, (state) => ({ + on(loadCurrentUser, (state) => ({ ...state, status: 'loading' as const })), - on(loadUserSuccess, (state, { response }) => ({ + on(loadCurrentUserSuccess, (state, { response }) => ({ ...state, user: response.user, error: null, status: 'success' as const })), - on(userApiError, (state, { error }) => ({ + on(currentUserApiError, (state, { error }) => ({ ...state, error: error, status: 'error' as const })), - on(clearUserApiError, (state) => ({ + on(clearCurrentUserApiError, (state) => ({ ...state, error: null, status: 'pending' as const diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.selectors.ts similarity index 75% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.selectors.ts rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.selectors.ts index b8e8efad6b..1dca25eff8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/user.selectors.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.selectors.ts @@ -16,8 +16,8 @@ */ import { createFeatureSelector, createSelector } from '@ngrx/store'; -import { userFeatureKey, UserState } from './index'; +import { currentUserFeatureKey, CurrentUserState } from './index'; -export const selectUserState = createFeatureSelector(userFeatureKey); +export const selectCurrentUserState = createFeatureSelector(currentUserFeatureKey); -export const selectUser = createSelector(selectUserState, (state: UserState) => state.user); +export const selectCurrentUser = createSelector(selectCurrentUserState, (state: CurrentUserState) => state.user); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/index.ts similarity index 88% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/index.ts rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/index.ts index 2d4f1207ff..ecdd9520bd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/user/index.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/index.ts @@ -17,10 +17,10 @@ import { Permissions, RequiredPermission } from '../shared'; -export const userFeatureKey = 'user'; +export const currentUserFeatureKey = 'currentUser'; -export interface LoadUserResponse { - user: User; +export interface LoadCurrentUserResponse { + user: CurrentUser; } export interface ComponentRestrictionPermission { @@ -28,7 +28,7 @@ export interface ComponentRestrictionPermission { permissions: Permissions; } -export interface User { +export interface CurrentUser { identity: string; anonymous: boolean; canVersionFlows: boolean; @@ -43,8 +43,8 @@ export interface User { componentRestrictionPermissions: ComponentRestrictionPermission[]; } -export interface UserState { - user: User; +export interface CurrentUserState { + user: CurrentUser; error: string | null; status: 'pending' | 'loading' | 'error' | 'success'; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts index db55e86177..4c2c59c406 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts @@ -17,8 +17,8 @@ import { routerReducer, RouterReducerState } from '@ngrx/router-store'; import { ActionReducerMap } from '@ngrx/store'; -import { UserState, userFeatureKey } from './user'; -import { userReducer } from './user/user.reducer'; +import { CurrentUserState, currentUserFeatureKey } from './current-user'; +import { currentUserReducer } from './current-user/current-user.reducer'; import { extensionTypesFeatureKey, ExtensionTypesState } from './extension-types'; import { extensionTypesReducer } from './extension-types/extension-types.reducer'; import { aboutFeatureKey, AboutState } from './about'; @@ -32,7 +32,7 @@ import { systemDiagnosticsReducer } from './system-diagnostics/system-diagnostic export interface NiFiState { router: RouterReducerState; - [userFeatureKey]: UserState; + [currentUserFeatureKey]: CurrentUserState; [extensionTypesFeatureKey]: ExtensionTypesState; [aboutFeatureKey]: AboutState; [statusHistoryFeatureKey]: StatusHistoryState; @@ -42,7 +42,7 @@ export interface NiFiState { export const rootReducers: ActionReducerMap = { router: routerReducer, - [userFeatureKey]: userReducer, + [currentUserFeatureKey]: currentUserReducer, [extensionTypesFeatureKey]: extensionTypesReducer, [aboutFeatureKey]: aboutReducer, [statusHistoryFeatureKey]: statusHistoryReducer, 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 7a0c6d48b2..64505982fc 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 @@ -49,6 +49,58 @@ export interface EditParameterResponse { parameter: Parameter; } +export interface UserEntity { + id: string; + permissions: Permissions; + component: User; + revision: Revision; + uri: string; +} + +export interface User extends Tenant { + userGroups: TenantEntity[]; + accessPolicies: AccessPolicySummaryEntity[]; +} + +export interface UserGroupEntity { + id: string; + permissions: Permissions; + component: UserGroup; + revision: Revision; + uri: string; +} + +export interface UserGroup extends Tenant { + users: TenantEntity[]; + accessPolicies: AccessPolicySummaryEntity[]; +} + +export interface TenantEntity { + id: string; + revision: Revision; + permissions: Permissions; + component: Tenant; +} + +export interface Tenant { + id: string; + identity: string; + configurable: boolean; +} + +export interface EditTenantRequest { + user?: UserEntity; + userGroup?: UserGroupEntity; + existingUsers: UserEntity[]; + existingUserGroups: UserGroupEntity[]; +} + +export interface EditTenantResponse { + revision: Revision; + user?: any; + userGroup?: any; +} + export interface CreateControllerServiceRequest { controllerServiceTypes: DocumentedType[]; } @@ -352,6 +404,35 @@ export interface ControllerServiceEntity { component: any; } +export interface AccessPolicySummaryEntity { + id: string; + component: AccessPolicySummary; + revision: Revision; + permissions: Permissions; +} + +export interface AccessPolicySummary { + id: string; + resource: string; + action: string; + componentReference?: ComponentReferenceEntity; + configurable: boolean; +} + +export interface ComponentReferenceEntity { + id: string; + parentGroupId?: string; + component: ComponentReference; + revision: Revision; + permissions: Permissions; +} + +export interface ComponentReference { + id: string; + parentGroupId?: string; + name: string; +} + export interface DocumentedType { bundle: Bundle; description?: string; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.html new file mode 100644 index 0000000000..494fdfcea1 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.html @@ -0,0 +1,63 @@ + + +

{{ isNew ? 'Add' : 'Edit' }} {{ isUser ? 'User' : 'User Group' }}

+
+ +
+ + Individual + Group + +
+
+ + Identity + + {{ getIdentityErrorMessage() }} + +
+
+ Member of + + {{ userGroup.component.identity }} + + +
+ +
+ Members + + {{ user.component.identity }} + + +
+
+
+ + + + +
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.scss new file mode 100644 index 0000000000..574d6a0ebc --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.scss @@ -0,0 +1,39 @@ +/* + * 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-tenant-form { + @include mat.button-density(-1); + + .mat-mdc-radio-button ~ .mat-mdc-radio-button { + margin-left: 16px; + } + + .mat-mdc-form-field { + width: 100%; + } + + .mat-mdc-form-field-error { + font-size: 12px; + } + + mat-selection-list { + max-height: 300px; + overflow: auto; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.spec.ts new file mode 100644 index 0000000000..c23f18a12b --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.spec.ts @@ -0,0 +1,798 @@ +/* + * 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 { EditTenantDialog } from './edit-tenant-dialog.component'; +import { EditTenantRequest } from '../../../state/shared'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +describe('EditTenantDialog', () => { + let component: EditTenantDialog; + let fixture: ComponentFixture; + + const data: EditTenantRequest = { + user: { + revision: { + version: 0 + }, + id: 'acfc1479-018c-1000-1025-6cc5b4adefb8', + uri: 'https://localhost:4200/nifi-api/tenants/users/acfc1479-018c-1000-1025-6cc5b4adefb8', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfc1479-018c-1000-1025-6cc5b4adefb8', + identity: 'Group 2', + configurable: true, + userGroups: [], + accessPolicies: [] + } + }, + existingUsers: [ + { + revision: { + version: 0 + }, + id: 'ad0ddd93-018c-1000-4e40-8e3b207abdcd', + uri: 'https://localhost:4200/nifi-api/tenants/users/ad0ddd93-018c-1000-4e40-8e3b207abdcd', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'ad0ddd93-018c-1000-4e40-8e3b207abdcd', + identity: 'user 1', + configurable: true, + userGroups: [ + { + revision: { + version: 0 + }, + id: 'acfbfa2c-018c-1000-0311-47b83e34c9c3', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfbfa2c-018c-1000-0311-47b83e34c9c3', + identity: 'group 1', + configurable: true + } + } + ], + accessPolicies: [ + { + revision: { + clientId: 'b09bd713-018c-1000-e5b8-14855e466f1b', + version: 4 + }, + id: 'b0c3148d-018c-1000-2cfe-8fab902c11f7', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'b0c3148d-018c-1000-2cfe-8fab902c11f7', + resource: '/system', + action: 'read', + configurable: true + } + } + ] + } + }, + { + revision: { + version: 0 + }, + id: 'ad0875a3-018c-1000-1877-3f4ebf93f91e', + uri: 'https://localhost:4200/nifi-api/tenants/users/ad0875a3-018c-1000-1877-3f4ebf93f91e', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'ad0875a3-018c-1000-1877-3f4ebf93f91e', + identity: 'user 2', + configurable: true, + userGroups: [ + { + revision: { + version: 0 + }, + id: 'acfbfa2c-018c-1000-0311-47b83e34c9c3', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfbfa2c-018c-1000-0311-47b83e34c9c3', + identity: 'group 1', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: 'acfcdcf6-018c-1000-604f-84da632fdbd5', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfcdcf6-018c-1000-604f-84da632fdbd5', + identity: 'group 9', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: 'a69482a2-018c-1000-2c02-fac31f4b102b', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'a69482a2-018c-1000-2c02-fac31f4b102b', + identity: 'Group', + configurable: true + } + } + ], + accessPolicies: [ + { + revision: { + clientId: 'b09bd713-018c-1000-e5b8-14855e466f1b', + version: 4 + }, + id: 'b0c3148d-018c-1000-2cfe-8fab902c11f7', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'b0c3148d-018c-1000-2cfe-8fab902c11f7', + resource: '/system', + action: 'read', + configurable: true + } + } + ] + } + }, + { + revision: { + version: 0 + }, + id: 'acfc1479-018c-1000-1025-6cc5b4adefb8', + uri: 'https://localhost:4200/nifi-api/tenants/users/acfc1479-018c-1000-1025-6cc5b4adefb8', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfc1479-018c-1000-1025-6cc5b4adefb8', + identity: 'Group 2', + configurable: true, + userGroups: [], + accessPolicies: [] + } + }, + { + revision: { + version: 0 + }, + id: 'acfc879d-018c-1000-c93e-21350df0f5bf', + uri: 'https://localhost:4200/nifi-api/tenants/users/acfc879d-018c-1000-c93e-21350df0f5bf', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfc879d-018c-1000-c93e-21350df0f5bf', + identity: 'group 7', + configurable: true, + userGroups: [], + accessPolicies: [] + } + }, + { + revision: { + version: 0 + }, + id: '21232f29-7a57-35a7-8389-4a0e4a801fc3', + uri: 'https://localhost:4200/nifi-api/tenants/users/21232f29-7a57-35a7-8389-4a0e4a801fc3', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: '21232f29-7a57-35a7-8389-4a0e4a801fc3', + identity: 'admin', + configurable: true, + userGroups: [ + { + revision: { + version: 0 + }, + id: 'a69482a2-018c-1000-2c02-fac31f4b102b', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'a69482a2-018c-1000-2c02-fac31f4b102b', + identity: 'Group', + configurable: true + } + } + ], + accessPolicies: [ + { + revision: { + clientId: 'b09bd713-018c-1000-e5b8-14855e466f1b', + version: 4 + }, + id: 'b0c3148d-018c-1000-2cfe-8fab902c11f7', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'b0c3148d-018c-1000-2cfe-8fab902c11f7', + resource: '/system', + action: 'read', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: '15e4e0bd-cb28-34fd-8587-f8d15162cba5', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: '15e4e0bd-cb28-34fd-8587-f8d15162cba5', + resource: '/tenants', + action: 'write', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: 'c6322e6c-4cc1-3bcc-91b3-2ed2111674cf', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'c6322e6c-4cc1-3bcc-91b3-2ed2111674cf', + resource: '/controller', + action: 'write', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: 'f99bccd1-a30e-3e4a-98a2-dbc708edc67f', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'f99bccd1-a30e-3e4a-98a2-dbc708edc67f', + resource: '/flow', + action: 'read', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: '627410be-1717-35b4-a06f-e9362b89e0b7', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: '627410be-1717-35b4-a06f-e9362b89e0b7', + resource: '/tenants', + action: 'read', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: '2e1015cb-0fed-3005-8e0d-722311f21a03', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: '2e1015cb-0fed-3005-8e0d-722311f21a03', + resource: '/controller', + action: 'read', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: 'b8775bd4-704a-34c6-987b-84f2daf7a515', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'b8775bd4-704a-34c6-987b-84f2daf7a515', + resource: '/restricted-components', + action: 'write', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: '92db2c23-018c-1000-8885-b650a16dcb32', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: '92db2c23-018c-1000-8885-b650a16dcb32', + resource: '/process-groups/92dade11-018c-1000-91a9-ad537020d5cb', + action: 'read', + componentReference: { + revision: { + version: 0 + }, + id: '92dade11-018c-1000-91a9-ad537020d5cb', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: '92dade11-018c-1000-91a9-ad537020d5cb', + name: 'NiFi Flow' + } + }, + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: '92db4367-018c-1000-e220-2dbec57c389b', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: '92db4367-018c-1000-e220-2dbec57c389b', + resource: '/process-groups/92dade11-018c-1000-91a9-ad537020d5cb', + action: 'write', + componentReference: { + revision: { + version: 0 + }, + id: '92dade11-018c-1000-91a9-ad537020d5cb', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: '92dade11-018c-1000-91a9-ad537020d5cb', + name: 'NiFi Flow' + } + }, + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: 'ad99ea98-3af6-3561-ae27-5bf09e1d969d', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'ad99ea98-3af6-3561-ae27-5bf09e1d969d', + resource: '/policies', + action: 'write', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: 'ff96062a-fa99-36dc-9942-0f6442ae7212', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'ff96062a-fa99-36dc-9942-0f6442ae7212', + resource: '/policies', + action: 'read', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: 'b1d4bb80-018c-1000-6cb7-a1e977f8382b', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'b1d4bb80-018c-1000-6cb7-a1e977f8382b', + resource: '/processors/ad55f343-018c-1000-1291-1992288346e5', + action: 'read', + componentReference: { + revision: { + version: 0 + }, + id: 'ad55f343-018c-1000-1291-1992288346e5', + permissions: { + canRead: true, + canWrite: true + }, + parentGroupId: '92dade11-018c-1000-91a9-ad537020d5cb', + component: { + id: 'ad55f343-018c-1000-1291-1992288346e5', + parentGroupId: '92dade11-018c-1000-91a9-ad537020d5cb', + name: 'InvokeHTTP' + } + }, + configurable: true + } + } + ] + } + }, + { + revision: { + version: 0 + }, + id: 'abf8bb43-018c-1000-3e0c-a4405f39c934', + uri: 'https://localhost:4200/nifi-api/tenants/users/abf8bb43-018c-1000-3e0c-a4405f39c934', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'abf8bb43-018c-1000-3e0c-a4405f39c934', + identity: 'test', + configurable: true, + userGroups: [ + { + revision: { + version: 0 + }, + id: 'a69482a2-018c-1000-2c02-fac31f4b102b', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'a69482a2-018c-1000-2c02-fac31f4b102b', + identity: 'Group', + configurable: true + } + } + ], + accessPolicies: [] + } + }, + { + revision: { + version: 0 + }, + id: 'acfc259e-018c-1000-2e1e-01919d4b0546', + uri: 'https://localhost:4200/nifi-api/tenants/users/acfc259e-018c-1000-2e1e-01919d4b0546', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfc259e-018c-1000-2e1e-01919d4b0546', + identity: 'group 3', + configurable: true, + userGroups: [], + accessPolicies: [] + } + }, + { + revision: { + version: 0 + }, + id: 'acfca1a8-018c-1000-6852-0dd013e20ee7', + uri: 'https://localhost:4200/nifi-api/tenants/users/acfca1a8-018c-1000-6852-0dd013e20ee7', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfca1a8-018c-1000-6852-0dd013e20ee7', + identity: 'group 8', + configurable: true, + userGroups: [], + accessPolicies: [] + } + }, + { + revision: { + version: 0 + }, + id: 'acfc5a70-018c-1000-a702-b52236aaacea', + uri: 'https://localhost:4200/nifi-api/tenants/users/acfc5a70-018c-1000-a702-b52236aaacea', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfc5a70-018c-1000-a702-b52236aaacea', + identity: 'group 5', + configurable: true, + userGroups: [], + accessPolicies: [] + } + }, + { + revision: { + version: 0 + }, + id: 'acfc6ee7-018c-1000-feed-fb1b89482663', + uri: 'https://localhost:4200/nifi-api/tenants/users/acfc6ee7-018c-1000-feed-fb1b89482663', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfc6ee7-018c-1000-feed-fb1b89482663', + identity: 'group 6', + configurable: true, + userGroups: [], + accessPolicies: [] + } + }, + { + revision: { + version: 0 + }, + id: 'acfc418b-018c-1000-bd42-50d38a5f8ec4', + uri: 'https://localhost:4200/nifi-api/tenants/users/acfc418b-018c-1000-bd42-50d38a5f8ec4', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfc418b-018c-1000-bd42-50d38a5f8ec4', + identity: 'group 4', + configurable: true, + userGroups: [], + accessPolicies: [] + } + } + ], + existingUserGroups: [ + { + revision: { + version: 0 + }, + id: 'acfbfa2c-018c-1000-0311-47b83e34c9c3', + uri: 'https://localhost:4200/nifi-api/tenants/user-groups/acfbfa2c-018c-1000-0311-47b83e34c9c3', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfbfa2c-018c-1000-0311-47b83e34c9c3', + identity: 'group 1', + configurable: true, + users: [ + { + revision: { + version: 0 + }, + id: 'ad0ddd93-018c-1000-4e40-8e3b207abdcd', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'ad0ddd93-018c-1000-4e40-8e3b207abdcd', + identity: 'user 1', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: 'ad0875a3-018c-1000-1877-3f4ebf93f91e', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'ad0875a3-018c-1000-1877-3f4ebf93f91e', + identity: 'user 2', + configurable: true + } + } + ], + accessPolicies: [ + { + revision: { + clientId: 'b09bd713-018c-1000-e5b8-14855e466f1b', + version: 4 + }, + id: 'b0c3148d-018c-1000-2cfe-8fab902c11f7', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'b0c3148d-018c-1000-2cfe-8fab902c11f7', + resource: '/system', + action: 'read', + configurable: true + } + } + ] + } + }, + { + revision: { + version: 0 + }, + id: 'acfcdcf6-018c-1000-604f-84da632fdbd5', + uri: 'https://localhost:4200/nifi-api/tenants/user-groups/acfcdcf6-018c-1000-604f-84da632fdbd5', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'acfcdcf6-018c-1000-604f-84da632fdbd5', + identity: 'group 9', + configurable: true, + users: [ + { + revision: { + version: 0 + }, + id: 'ad0875a3-018c-1000-1877-3f4ebf93f91e', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'ad0875a3-018c-1000-1877-3f4ebf93f91e', + identity: 'user 2', + configurable: true + } + } + ], + accessPolicies: [] + } + }, + { + revision: { + version: 0 + }, + id: 'a69482a2-018c-1000-2c02-fac31f4b102b', + uri: 'https://localhost:4200/nifi-api/tenants/user-groups/a69482a2-018c-1000-2c02-fac31f4b102b', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'a69482a2-018c-1000-2c02-fac31f4b102b', + identity: 'Group', + configurable: true, + users: [ + { + revision: { + version: 0 + }, + id: 'ad0875a3-018c-1000-1877-3f4ebf93f91e', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'ad0875a3-018c-1000-1877-3f4ebf93f91e', + identity: 'user 2', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: '21232f29-7a57-35a7-8389-4a0e4a801fc3', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: '21232f29-7a57-35a7-8389-4a0e4a801fc3', + identity: 'admin', + configurable: true + } + }, + { + revision: { + version: 0 + }, + id: 'abf8bb43-018c-1000-3e0c-a4405f39c934', + permissions: { + canRead: true, + canWrite: true + }, + component: { + id: 'abf8bb43-018c-1000-3e0c-a4405f39c934', + identity: 'test', + configurable: true + } + } + ], + accessPolicies: [] + } + } + ] + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [EditTenantDialog, BrowserAnimationsModule], + providers: [{ provide: MAT_DIALOG_DATA, useValue: data }] + }); + fixture = TestBed.createComponent(EditTenantDialog); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.ts new file mode 100644 index 0000000000..a972185469 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.ts @@ -0,0 +1,256 @@ +/* + * 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 { EditTenantRequest, EditTenantResponse, Revision, UserEntity, UserGroupEntity } from '../../../state/shared'; +import { MatButtonModule } from '@angular/material/button'; +import { + AbstractControl, + FormBuilder, + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule, + ValidationErrors, + ValidatorFn, + Validators +} from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { NifiSpinnerDirective } from '../spinner/nifi-spinner.directive'; +import { AsyncPipe, NgForOf, NgIf } from '@angular/common'; +import { Observable } from 'rxjs'; +import { MatListModule } from '@angular/material/list'; +import { Client } from '../../../service/client.service'; +import { NiFiCommon } from '../../../service/nifi-common.service'; + +@Component({ + selector: 'edit-tenant-dialog', + standalone: true, + imports: [ + MatDialogModule, + MatButtonModule, + FormsModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + MatRadioModule, + MatCheckboxModule, + NifiSpinnerDirective, + NgIf, + AsyncPipe, + MatListModule, + NgForOf + ], + templateUrl: './edit-tenant-dialog.component.html', + styleUrls: ['./edit-tenant-dialog.component.scss'] +}) +export class EditTenantDialog { + @Input() saving$!: Observable; + @Output() editTenant: EventEmitter = new EventEmitter(); + @Output() cancel: EventEmitter = new EventEmitter(); + + readonly USER: string = 'user'; + readonly USER_GROUP: string = 'userGroup'; + isUser: boolean = true; + + identity: FormControl; + tenantType: FormControl; + editTenantForm: FormGroup; + isNew: boolean; + + users: UserEntity[]; + userGroups: UserGroupEntity[]; + + constructor( + @Inject(MAT_DIALOG_DATA) private request: EditTenantRequest, + private formBuilder: FormBuilder, + private nifiCommon: NiFiCommon, + private client: Client + ) { + const user: UserEntity | undefined = request.user; + const userGroup: UserGroupEntity | undefined = request.userGroup; + + if (user || userGroup) { + this.isNew = false; + + let identity: string = ''; + let tenantType: string = this.USER; + if (user) { + identity = user.component.identity; + } else if (userGroup) { + identity = userGroup.component.identity; + tenantType = this.USER_GROUP; + } + this.identity = new FormControl(identity, [ + Validators.required, + this.existingTenantValidator(request.existingUsers, request.existingUserGroups, identity) + ]); + this.tenantType = new FormControl({ value: tenantType, disabled: true }); + } else { + this.isNew = true; + + this.identity = new FormControl('', [ + Validators.required, + this.existingTenantValidator(request.existingUsers, request.existingUserGroups) + ]); + this.tenantType = new FormControl(this.USER); + } + + this.users = request.existingUsers.slice().sort((a: UserEntity, b: UserEntity) => { + return this.nifiCommon.compareString(a.component.identity, b.component.identity); + }); + this.userGroups = request.existingUserGroups.slice().sort((a: UserGroupEntity, b: UserGroupEntity) => { + return this.nifiCommon.compareString(a.component.identity, b.component.identity); + }); + + this.editTenantForm = this.formBuilder.group({ + identity: this.identity, + tenantType: this.tenantType + }); + + this.tenantTypeChanged(); + } + + private existingTenantValidator( + existingUsers: UserEntity[], + existingUserGroups: UserGroupEntity[], + currentIdentity?: string + ): ValidatorFn { + const existingUserNames: string[] = existingUsers.map((user) => user.component.identity); + const existingUserGroupNames: string[] = existingUserGroups.map((userGroup) => userGroup.component.identity); + + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value; + if (value === '') { + return null; + } + + const existingTenants: string[] = this.isUser ? existingUserNames : existingUserGroupNames; + if (existingTenants.includes(value) && value != currentIdentity) { + return { + existingTenant: true + }; + } + return null; + }; + } + + getIdentityErrorMessage(): string { + if (this.identity.hasError('required')) { + return 'Identity is required.'; + } + + const tenantType: string = this.isUser ? 'user' : 'user group'; + return this.identity.hasError('existingTenant') ? `A ${tenantType} with this identity already exists.` : ''; + } + + tenantTypeChanged(): void { + this.isUser = this.editTenantForm.get('tenantType')?.value == this.USER; + if (this.isUser) { + this.setupFormWithExistingUserGroups(); + } else { + this.setupFormWithExistingUsers(); + } + } + + setupFormWithExistingUsers(): void { + if (this.editTenantForm.contains('userGroups')) { + this.editTenantForm.removeControl('userGroups'); + } + + let users: string[] = []; + if (this.request.userGroup) { + users.push(...this.request.userGroup.component.users.map((user) => user.id)); + } + + this.editTenantForm.addControl('users', new FormControl(users)); + } + + setupFormWithExistingUserGroups(): void { + if (this.editTenantForm.contains('users')) { + this.editTenantForm.removeControl('users'); + } + + let userGroups: string[] = []; + if (this.request.user) { + userGroups.push(...this.request.user.component.userGroups.map((userGroup) => userGroup.id)); + } + + this.editTenantForm.addControl('userGroups', new FormControl(userGroups)); + } + + cancelClicked(): void { + this.cancel.next(); + } + + okClicked(): void { + if (this.isUser) { + const revision: Revision = this.isNew + ? { + clientId: this.client.getClientId(), + version: 0 + } + : this.client.getRevision(this.request.user); + + const userGroupsAdded: string[] = []; + const userGroupsRemoved: string[] = []; + const userGroupsSelected: string[] = this.editTenantForm.get('userGroups')?.value; + if (this.request.user) { + const userGroups: string[] = this.request.user.component.userGroups.map((userGroup) => userGroup.id); + + userGroupsAdded.push(...userGroupsSelected.filter((x) => !userGroups.includes(x))); + userGroupsRemoved.push(...userGroups.filter((x) => !userGroupsSelected.includes(x))); + } else { + userGroupsAdded.push(...userGroupsSelected); + } + + this.editTenant.next({ + revision, + user: { + payload: { + identity: this.editTenantForm.get('identity')?.value + }, + userGroupsAdded, + userGroupsRemoved + } + }); + } else { + const revision: Revision = this.isNew + ? { + clientId: this.client.getClientId(), + version: 0 + } + : this.client.getRevision(this.request.userGroup); + + const users: string[] = this.editTenantForm.get('users')?.value; + + this.editTenant.next({ + revision, + userGroup: { + payload: { + identity: this.editTenantForm.get('identity')?.value + }, + users + } + }); + } + } +}