mirror of https://github.com/apache/nifi.git
NIFI-12548: Policy Management (#8225)
* NIFI-12548: - Global Access Policies. * NIFI-12548: - Component Access Policies. * NIFI-12548: - Addressing review feedback. * NIFI-12548: - Addressing review feedback. This closes #8225
This commit is contained in:
parent
9dd832e150
commit
0a3393b091
|
@ -52,6 +52,12 @@ const routes: Routes = [
|
|||
canMatch: [authenticationGuard],
|
||||
loadChildren: () => import('./pages/users/feature/users.module').then((m) => m.UsersModule)
|
||||
},
|
||||
{
|
||||
path: 'access-policies',
|
||||
canMatch: [authenticationGuard],
|
||||
loadChildren: () =>
|
||||
import('./pages/access-policies/feature/access-policies.module').then((m) => m.AccessPoliciesModule)
|
||||
},
|
||||
{
|
||||
path: 'summary',
|
||||
canMatch: [authenticationGuard],
|
||||
|
|
|
@ -40,6 +40,7 @@ import { StatusHistoryEffects } from './state/status-history/status-history.effe
|
|||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { ControllerServiceStateEffects } from './state/contoller-service-state/controller-service-state.effects';
|
||||
import { SystemDiagnosticsEffects } from './state/system-diagnostics/system-diagnostics.effects';
|
||||
import { FlowConfigurationEffects } from './state/flow-configuration/flow-configuration.effects';
|
||||
|
||||
// @ts-ignore
|
||||
@NgModule({
|
||||
|
@ -62,6 +63,7 @@ import { SystemDiagnosticsEffects } from './state/system-diagnostics/system-diag
|
|||
CurrentUserEffects,
|
||||
ExtensionTypesEffects,
|
||||
AboutEffects,
|
||||
FlowConfigurationEffects,
|
||||
StatusHistoryEffects,
|
||||
ControllerServiceStateEffects,
|
||||
SystemDiagnosticsEffects
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { AccessPolicies } from './access-policies.component';
|
||||
import { FlowConfiguration } from '../../../state/flow-configuration';
|
||||
import { checkFlowConfiguration } from '../../../service/guard/flow-configuration.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AccessPolicies,
|
||||
canMatch: [
|
||||
checkFlowConfiguration(
|
||||
(flowConfiguration: FlowConfiguration) => flowConfiguration.supportsManagedAuthorizer
|
||||
)
|
||||
],
|
||||
children: [
|
||||
{
|
||||
path: 'global',
|
||||
loadChildren: () =>
|
||||
import('../ui/global-access-policies/global-access-policies.module').then(
|
||||
(m) => m.GlobalAccessPoliciesModule
|
||||
)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () =>
|
||||
import('../ui/component-access-policies/component-access-policies.module').then(
|
||||
(m) => m.ComponentAccessPoliciesModule
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AccessPoliciesRoutingModule {}
|
|
@ -0,0 +1,28 @@
|
|||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="p-4 flex flex-col h-screen justify-between gap-y-5">
|
||||
<div class="flex justify-between">
|
||||
<h3 class="text-xl bold access-policies-header">Access Policies</h3>
|
||||
<button class="nifi-button" [routerLink]="['/']">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
|
@ -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.
|
||||
*/
|
||||
|
||||
.access-policies-header {
|
||||
color: #728e9b;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { AccessPolicies } from './access-policies.component';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../state/access-policy/access-policy.reducer';
|
||||
|
||||
describe('AccessPolicies', () => {
|
||||
let component: AccessPolicies;
|
||||
let fixture: ComponentFixture<AccessPolicies>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AccessPolicies],
|
||||
imports: [RouterModule, RouterTestingModule],
|
||||
providers: [
|
||||
provideMockStore({
|
||||
initialState
|
||||
})
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(AccessPolicies);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
@Component({
|
||||
selector: 'access-policies',
|
||||
templateUrl: './access-policies.component.html',
|
||||
styleUrls: ['./access-policies.component.scss']
|
||||
})
|
||||
export class AccessPolicies implements OnInit, OnDestroy {
|
||||
constructor(private store: Store<NiFiState>) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(startCurrentUserPolling());
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.store.dispatch(stopCurrentUserPolling());
|
||||
}
|
||||
}
|
|
@ -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 { AccessPolicies } from './access-policies.component';
|
||||
import { AccessPoliciesRoutingModule } from './access-policies-routing.module';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { reducers, accessPoliciesFeatureKey } from '../state';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { AccessPolicyEffects } from '../state/access-policy/access-policy.effects';
|
||||
import { TenantsEffects } from '../state/tenants/tenants.effects';
|
||||
import { PolicyComponentEffects } from '../state/policy-component/policy-component.effects';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AccessPolicies],
|
||||
exports: [AccessPolicies],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AccessPoliciesRoutingModule,
|
||||
StoreModule.forFeature(accessPoliciesFeatureKey, reducers),
|
||||
EffectsModule.forFeature(AccessPolicyEffects, TenantsEffects, PolicyComponentEffects),
|
||||
MatDialogModule
|
||||
]
|
||||
})
|
||||
export class AccessPoliciesModule {}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Client } from '../../../service/client.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AccessPolicyEntity, ComponentResourceAction, ResourceAction } from '../state/shared';
|
||||
import { NiFiCommon } from '../../../service/nifi-common.service';
|
||||
import { TenantEntity } from '../../../state/shared';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AccessPolicyService {
|
||||
private static readonly API: string = '../nifi-api';
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private client: Client,
|
||||
private nifiCommon: NiFiCommon
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 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, ':');
|
||||
}
|
||||
|
||||
createAccessPolicy(resourceAction: ResourceAction): Observable<any> {
|
||||
let resource: string = `/${resourceAction.resource}`;
|
||||
if (resourceAction.resourceIdentifier) {
|
||||
resource += `/${resourceAction.resourceIdentifier}`;
|
||||
}
|
||||
|
||||
const payload: unknown = {
|
||||
revision: {
|
||||
version: 0,
|
||||
clientId: this.client.getClientId()
|
||||
},
|
||||
component: {
|
||||
action: resourceAction.action,
|
||||
resource,
|
||||
userGroups: [],
|
||||
users: []
|
||||
}
|
||||
};
|
||||
|
||||
return this.httpClient.post(`${AccessPolicyService.API}/policies`, payload);
|
||||
}
|
||||
|
||||
getAccessPolicy(resourceAction: ResourceAction): Observable<any> {
|
||||
const path: string[] = [resourceAction.action, resourceAction.resource];
|
||||
if (resourceAction.resourceIdentifier) {
|
||||
path.push(resourceAction.resourceIdentifier);
|
||||
}
|
||||
return this.httpClient.get(`${AccessPolicyService.API}/policies/${path.join('/')}`);
|
||||
}
|
||||
|
||||
getPolicyComponent(resourceAction: ComponentResourceAction): Observable<any> {
|
||||
return this.httpClient.get(
|
||||
`${AccessPolicyService.API}/${resourceAction.resource}/${resourceAction.resourceIdentifier}`
|
||||
);
|
||||
}
|
||||
|
||||
updateAccessPolicy(accessPolicy: AccessPolicyEntity, users: TenantEntity[], userGroups: TenantEntity[]) {
|
||||
const payload: unknown = {
|
||||
revision: this.client.getRevision(accessPolicy),
|
||||
component: {
|
||||
id: accessPolicy.id,
|
||||
userGroups,
|
||||
users
|
||||
}
|
||||
};
|
||||
|
||||
return this.httpClient.put(this.stripProtocol(accessPolicy.uri), payload);
|
||||
}
|
||||
|
||||
deleteAccessPolicy(accessPolicy: AccessPolicyEntity): Observable<any> {
|
||||
const revision: any = this.client.getRevision(accessPolicy);
|
||||
return this.httpClient.delete(this.stripProtocol(accessPolicy.uri), { params: revision });
|
||||
}
|
||||
|
||||
getUsers(): Observable<any> {
|
||||
return this.httpClient.get(`${AccessPolicyService.API}/tenants/users`);
|
||||
}
|
||||
|
||||
getUserGroups(): Observable<any> {
|
||||
return this.httpClient.get(`${AccessPolicyService.API}/tenants/user-groups`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 {
|
||||
LoadAccessPolicyError,
|
||||
LoadAccessPolicyRequest,
|
||||
AccessPolicyResponse,
|
||||
SelectGlobalAccessPolicyRequest,
|
||||
SetAccessPolicyRequest,
|
||||
ResetAccessPolicy,
|
||||
RemoveTenantFromPolicyRequest,
|
||||
AddTenantsToPolicyRequest,
|
||||
SelectComponentAccessPolicyRequest
|
||||
} from './index';
|
||||
|
||||
const ACCESS_POLICY_PREFIX: string = '[Access Policy]';
|
||||
|
||||
export const selectGlobalAccessPolicy = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Select Global Access Policy`,
|
||||
props<{ request: SelectGlobalAccessPolicyRequest }>()
|
||||
);
|
||||
|
||||
export const selectComponentAccessPolicy = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Select Component Access Policy`,
|
||||
props<{ request: SelectComponentAccessPolicyRequest }>()
|
||||
);
|
||||
|
||||
export const setAccessPolicy = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Set Access Policy`,
|
||||
props<{ request: SetAccessPolicyRequest }>()
|
||||
);
|
||||
|
||||
export const reloadAccessPolicy = createAction(`${ACCESS_POLICY_PREFIX} Reload Access Policy`);
|
||||
|
||||
export const loadAccessPolicy = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Load Access Policy`,
|
||||
props<{ request: LoadAccessPolicyRequest }>()
|
||||
);
|
||||
|
||||
export const loadAccessPolicySuccess = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Load Access Policy Success`,
|
||||
props<{ response: AccessPolicyResponse }>()
|
||||
);
|
||||
|
||||
export const createAccessPolicy = createAction(`${ACCESS_POLICY_PREFIX} Create Access Policy`);
|
||||
|
||||
export const createAccessPolicySuccess = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Create Access Policy Success`,
|
||||
props<{ response: AccessPolicyResponse }>()
|
||||
);
|
||||
|
||||
export const openAddTenantToPolicyDialog = createAction(`${ACCESS_POLICY_PREFIX} Open Add Tenant To Policy Dialog`);
|
||||
|
||||
export const addTenantsToPolicy = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Add Tenants To Policy`,
|
||||
props<{ request: AddTenantsToPolicyRequest }>()
|
||||
);
|
||||
|
||||
export const promptRemoveTenantFromPolicy = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Prompt Remove Tenant From Policy`,
|
||||
props<{ request: RemoveTenantFromPolicyRequest }>()
|
||||
);
|
||||
|
||||
export const removeTenantFromPolicy = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Remove Tenant From Policy`,
|
||||
props<{ request: RemoveTenantFromPolicyRequest }>()
|
||||
);
|
||||
|
||||
export const promptDeleteAccessPolicy = createAction(`${ACCESS_POLICY_PREFIX} Prompt Delete Access Policy`);
|
||||
|
||||
export const deleteAccessPolicy = createAction(`${ACCESS_POLICY_PREFIX} Delete Access Policy`);
|
||||
|
||||
export const resetAccessPolicy = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Reset Access Policy`,
|
||||
props<{ response: ResetAccessPolicy }>()
|
||||
);
|
||||
|
||||
export const accessPolicyApiError = createAction(
|
||||
`${ACCESS_POLICY_PREFIX} Access Policy Api Error`,
|
||||
props<{ response: LoadAccessPolicyError }>()
|
||||
);
|
||||
|
||||
export const resetAccessPolicyState = createAction(`${ACCESS_POLICY_PREFIX} Reset Access Policy State`);
|
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
* 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 AccessPolicyActions from './access-policy.actions';
|
||||
import { catchError, combineLatest, filter, from, map, of, switchMap, take, tap, withLatestFrom } from 'rxjs';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { AccessPolicyService } from '../../service/access-policy.service';
|
||||
import { AccessPolicyEntity, ComponentResourceAction, PolicyStatus, ResourceAction } from '../shared';
|
||||
import { selectAccessPolicy, selectResourceAction, selectSaving } from './access-policy.selectors';
|
||||
import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
|
||||
import { TenantEntity } from '../../../../state/shared';
|
||||
import { AddTenantToPolicyDialog } from '../../ui/common/add-tenant-to-policy-dialog/add-tenant-to-policy-dialog.component';
|
||||
import { AddTenantsToPolicyRequest } from './index';
|
||||
import { ComponentAccessPolicies } from '../../ui/component-access-policies/component-access-policies.component';
|
||||
import { selectUserGroups, selectUsers } from '../tenants/tenants.selectors';
|
||||
|
||||
@Injectable()
|
||||
export class AccessPolicyEffects {
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private store: Store<NiFiState>,
|
||||
private router: Router,
|
||||
private accessPoliciesService: AccessPolicyService,
|
||||
private dialog: MatDialog
|
||||
) {}
|
||||
|
||||
setAccessPolicy$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.setAccessPolicy),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
of(
|
||||
AccessPolicyActions.loadAccessPolicy({
|
||||
request: {
|
||||
resourceAction: request.resourceAction
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
reloadAccessPolicy$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.reloadAccessPolicy),
|
||||
withLatestFrom(this.store.select(selectResourceAction)),
|
||||
filter(([action, resourceAction]) => resourceAction != null),
|
||||
switchMap(([action, resourceAction]) => {
|
||||
return of(
|
||||
AccessPolicyActions.loadAccessPolicy({
|
||||
request: {
|
||||
// @ts-ignore
|
||||
resourceAction
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
loadAccessPolicy$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.loadAccessPolicy),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
from(this.accessPoliciesService.getAccessPolicy(request.resourceAction)).pipe(
|
||||
map((response) => {
|
||||
const accessPolicy: AccessPolicyEntity = response;
|
||||
|
||||
let requestedResource: string = `/${request.resourceAction.resource}`;
|
||||
if (request.resourceAction.resourceIdentifier) {
|
||||
requestedResource += `/${request.resourceAction.resourceIdentifier}`;
|
||||
}
|
||||
|
||||
let policyStatus: PolicyStatus | undefined;
|
||||
if (accessPolicy.component.resource === requestedResource) {
|
||||
policyStatus = PolicyStatus.Found;
|
||||
} else {
|
||||
policyStatus = PolicyStatus.Inherited;
|
||||
}
|
||||
|
||||
return AccessPolicyActions.loadAccessPolicySuccess({
|
||||
response: {
|
||||
accessPolicy,
|
||||
policyStatus
|
||||
}
|
||||
});
|
||||
}),
|
||||
catchError((error) => {
|
||||
let policyStatus: PolicyStatus | undefined;
|
||||
if (error.status === 404) {
|
||||
policyStatus = PolicyStatus.NotFound;
|
||||
} else if (error.status === 403) {
|
||||
policyStatus = PolicyStatus.Forbidden;
|
||||
}
|
||||
|
||||
if (policyStatus) {
|
||||
return of(
|
||||
AccessPolicyActions.resetAccessPolicy({
|
||||
response: {
|
||||
policyStatus
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return of(
|
||||
AccessPolicyActions.accessPolicyApiError({
|
||||
response: {
|
||||
error: error.error
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
createAccessPolicy$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.createAccessPolicy),
|
||||
withLatestFrom(this.store.select(selectResourceAction)),
|
||||
filter(([action, resourceAction]) => resourceAction != null),
|
||||
switchMap(([action, resourceAction]) =>
|
||||
// @ts-ignore
|
||||
from(this.accessPoliciesService.createAccessPolicy(resourceAction)).pipe(
|
||||
map((response) => {
|
||||
const accessPolicy: AccessPolicyEntity = response;
|
||||
const policyStatus: PolicyStatus = PolicyStatus.Found;
|
||||
|
||||
return AccessPolicyActions.createAccessPolicySuccess({
|
||||
response: {
|
||||
accessPolicy,
|
||||
policyStatus
|
||||
}
|
||||
});
|
||||
}),
|
||||
catchError((error) =>
|
||||
of(
|
||||
AccessPolicyActions.accessPolicyApiError({
|
||||
response: {
|
||||
error: error.error
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
selectGlobalAccessPolicy$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.selectGlobalAccessPolicy),
|
||||
map((action) => action.request),
|
||||
tap((request) => {
|
||||
const resourceAction: ResourceAction = request.resourceAction;
|
||||
const commands: string[] = [
|
||||
'/access-policies',
|
||||
'global',
|
||||
resourceAction.action,
|
||||
resourceAction.resource
|
||||
];
|
||||
if (resourceAction.resourceIdentifier) {
|
||||
commands.push(resourceAction.resourceIdentifier);
|
||||
}
|
||||
|
||||
this.router.navigate(commands);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
selectComponentAccessPolicy$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.selectComponentAccessPolicy),
|
||||
map((action) => action.request),
|
||||
tap((request) => {
|
||||
const resourceAction: ComponentResourceAction = request.resourceAction;
|
||||
const commands: string[] = [
|
||||
'/access-policies',
|
||||
resourceAction.action,
|
||||
resourceAction.policy,
|
||||
resourceAction.resource,
|
||||
resourceAction.resourceIdentifier
|
||||
];
|
||||
this.router.navigate(commands);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
openAddTenantToPolicyDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.openAddTenantToPolicyDialog),
|
||||
withLatestFrom(this.store.select(selectAccessPolicy)),
|
||||
tap(([action, accessPolicy]) => {
|
||||
const dialogReference = this.dialog.open(AddTenantToPolicyDialog, {
|
||||
data: {
|
||||
accessPolicy
|
||||
},
|
||||
panelClass: 'medium-dialog'
|
||||
});
|
||||
|
||||
dialogReference.componentInstance.saving$ = this.store.select(selectSaving);
|
||||
dialogReference.componentInstance.users$ = this.store.select(selectUsers);
|
||||
dialogReference.componentInstance.userGroups$ = this.store.select(selectUserGroups);
|
||||
|
||||
dialogReference.componentInstance.addTenants
|
||||
.pipe(take(1))
|
||||
.subscribe((request: AddTenantsToPolicyRequest) => {
|
||||
this.store.dispatch(AccessPolicyActions.addTenantsToPolicy({ request }));
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
addTenantsToPolicy$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.addTenantsToPolicy),
|
||||
map((action) => action.request),
|
||||
withLatestFrom(this.store.select(selectAccessPolicy)),
|
||||
filter(([request, accessPolicyEntity]) => accessPolicyEntity != null),
|
||||
switchMap(([request, accessPolicyEntity]) => {
|
||||
// @ts-ignore
|
||||
const accessPolicy: AccessPolicyEntity = accessPolicyEntity;
|
||||
|
||||
const users: TenantEntity[] = [...accessPolicy.component.users, ...request.users];
|
||||
const userGroups: TenantEntity[] = [...accessPolicy.component.userGroups, ...request.userGroups];
|
||||
|
||||
return from(this.accessPoliciesService.updateAccessPolicy(accessPolicy, users, userGroups)).pipe(
|
||||
map((response: any) => {
|
||||
this.dialog.closeAll();
|
||||
|
||||
return AccessPolicyActions.loadAccessPolicySuccess({
|
||||
response: {
|
||||
accessPolicy: response,
|
||||
policyStatus: PolicyStatus.Found
|
||||
}
|
||||
});
|
||||
}),
|
||||
catchError((error) =>
|
||||
of(
|
||||
AccessPolicyActions.accessPolicyApiError({
|
||||
response: {
|
||||
error: error.error
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
promptRemoveTenantFromPolicy$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.promptRemoveTenantFromPolicy),
|
||||
map((action) => action.request),
|
||||
tap((request) => {
|
||||
const dialogReference = this.dialog.open(YesNoDialog, {
|
||||
data: {
|
||||
title: 'Update Policy',
|
||||
message: `Remove '${request.tenant.component.identity}' from this policy?`
|
||||
},
|
||||
panelClass: 'small-dialog'
|
||||
});
|
||||
|
||||
dialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => {
|
||||
this.store.dispatch(AccessPolicyActions.removeTenantFromPolicy({ request }));
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
removeTenantFromPolicy$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.removeTenantFromPolicy),
|
||||
map((action) => action.request),
|
||||
withLatestFrom(this.store.select(selectAccessPolicy)),
|
||||
filter(([request, accessPolicyEntity]) => accessPolicyEntity != null),
|
||||
switchMap(([request, accessPolicyEntity]) => {
|
||||
// @ts-ignore
|
||||
const accessPolicy: AccessPolicyEntity = accessPolicyEntity;
|
||||
|
||||
const users: TenantEntity[] = [...accessPolicy.component.users];
|
||||
const userGroups: TenantEntity[] = [...accessPolicy.component.userGroups];
|
||||
|
||||
let tenants: TenantEntity[];
|
||||
if (request.tenantType === 'user') {
|
||||
tenants = users;
|
||||
} else {
|
||||
tenants = userGroups;
|
||||
}
|
||||
|
||||
if (tenants) {
|
||||
const tenantIndex: number = tenants.findIndex(
|
||||
(tenant: TenantEntity) => request.tenant.id === tenant.id
|
||||
);
|
||||
if (tenantIndex > -1) {
|
||||
tenants.splice(tenantIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return from(this.accessPoliciesService.updateAccessPolicy(accessPolicy, users, userGroups)).pipe(
|
||||
map((response: any) => {
|
||||
return AccessPolicyActions.loadAccessPolicySuccess({
|
||||
response: {
|
||||
accessPolicy: response,
|
||||
policyStatus: PolicyStatus.Found
|
||||
}
|
||||
});
|
||||
}),
|
||||
catchError((error) =>
|
||||
of(
|
||||
AccessPolicyActions.accessPolicyApiError({
|
||||
response: {
|
||||
error: error.error
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
promptDeleteAccessPolicy$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.promptDeleteAccessPolicy),
|
||||
tap((request) => {
|
||||
const dialogReference = this.dialog.open(YesNoDialog, {
|
||||
data: {
|
||||
title: 'Delete Policy',
|
||||
message:
|
||||
'Are you sure you want to delete this policy? By doing so, the permissions for this component will revert to the inherited policy if applicable.'
|
||||
},
|
||||
panelClass: 'small-dialog'
|
||||
});
|
||||
|
||||
dialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => {
|
||||
this.store.dispatch(AccessPolicyActions.deleteAccessPolicy());
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
deleteAccessPolicy$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(AccessPolicyActions.deleteAccessPolicy),
|
||||
withLatestFrom(this.store.select(selectResourceAction), this.store.select(selectAccessPolicy)),
|
||||
filter(([action, resourceAction, accessPolicy]) => resourceAction != null && accessPolicy != null),
|
||||
switchMap(([action, resourceAction, accessPolicy]) =>
|
||||
// @ts-ignore
|
||||
from(this.accessPoliciesService.deleteAccessPolicy(accessPolicy)).pipe(
|
||||
map((response) => {
|
||||
// the policy was removed, we need to reload the policy for this resource and action to fetch
|
||||
// the inherited policy or correctly when it's not found
|
||||
return AccessPolicyActions.loadAccessPolicy({
|
||||
request: {
|
||||
// @ts-ignore
|
||||
resourceAction
|
||||
}
|
||||
});
|
||||
}),
|
||||
catchError((error) =>
|
||||
of(
|
||||
AccessPolicyActions.accessPolicyApiError({
|
||||
response: {
|
||||
error: error.error
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AccessPolicyState } from './index';
|
||||
import { createReducer, on } from '@ngrx/store';
|
||||
import {
|
||||
addTenantsToPolicy,
|
||||
createAccessPolicySuccess,
|
||||
accessPolicyApiError,
|
||||
loadAccessPolicy,
|
||||
loadAccessPolicySuccess,
|
||||
removeTenantFromPolicy,
|
||||
resetAccessPolicyState,
|
||||
resetAccessPolicy,
|
||||
setAccessPolicy
|
||||
} from './access-policy.actions';
|
||||
|
||||
export const initialState: AccessPolicyState = {
|
||||
saving: false,
|
||||
loadedTimestamp: '',
|
||||
error: null,
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
export const accessPolicyReducer = createReducer(
|
||||
initialState,
|
||||
on(setAccessPolicy, (state, { request }) => ({
|
||||
...state,
|
||||
resourceAction: request.resourceAction
|
||||
})),
|
||||
on(loadAccessPolicy, (state, { request }) => ({
|
||||
...state,
|
||||
status: 'loading' as const
|
||||
})),
|
||||
on(loadAccessPolicySuccess, createAccessPolicySuccess, (state, { response }) => ({
|
||||
...state,
|
||||
accessPolicy: response.accessPolicy,
|
||||
policyStatus: response.policyStatus,
|
||||
loadedTimestamp: response.accessPolicy.generated,
|
||||
saving: false,
|
||||
status: 'success' as const
|
||||
})),
|
||||
on(addTenantsToPolicy, (state, { request }) => ({
|
||||
...state,
|
||||
saving: true
|
||||
})),
|
||||
on(removeTenantFromPolicy, (state, { request }) => ({
|
||||
...state,
|
||||
saving: true
|
||||
})),
|
||||
on(resetAccessPolicy, (state, { response }) => ({
|
||||
...state,
|
||||
accessPolicy: undefined,
|
||||
policyStatus: response.policyStatus,
|
||||
loadedTimestamp: 'N/A',
|
||||
status: 'success' as const
|
||||
})),
|
||||
on(accessPolicyApiError, (state, { response }) => ({
|
||||
...state,
|
||||
error: response.error,
|
||||
accessPolicy: undefined,
|
||||
policyStatus: undefined,
|
||||
status: 'error' as const
|
||||
})),
|
||||
on(resetAccessPolicyState, (state) => ({
|
||||
...initialState
|
||||
}))
|
||||
);
|
|
@ -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 { createSelector } from '@ngrx/store';
|
||||
import { AccessPoliciesState, selectAccessPoliciesState } from '../index';
|
||||
import { accessPolicyFeatureKey, AccessPolicyState } from './index';
|
||||
import { selectCurrentRoute } from '../../../../state/router/router.selectors';
|
||||
import { ComponentResourceAction, ResourceAction } from '../shared';
|
||||
|
||||
export const selectAccessPolicyState = createSelector(
|
||||
selectAccessPoliciesState,
|
||||
(state: AccessPoliciesState) => state[accessPolicyFeatureKey]
|
||||
);
|
||||
|
||||
export const selectResourceAction = createSelector(
|
||||
selectAccessPolicyState,
|
||||
(state: AccessPolicyState) => state.resourceAction
|
||||
);
|
||||
|
||||
export const selectAccessPolicy = createSelector(
|
||||
selectAccessPolicyState,
|
||||
(state: AccessPolicyState) => state.accessPolicy
|
||||
);
|
||||
|
||||
export const selectSaving = createSelector(selectAccessPolicyState, (state: AccessPolicyState) => state.saving);
|
||||
|
||||
export const selectGlobalResourceActionFromRoute = createSelector(selectCurrentRoute, (route) => {
|
||||
let selectedResourceAction: ResourceAction | null = null;
|
||||
if (route?.params.action && route?.params.resource) {
|
||||
// always select the action and resource from the route
|
||||
selectedResourceAction = {
|
||||
action: route.params.action,
|
||||
resource: route.params.resource,
|
||||
resourceIdentifier: route.params.resourceIdentifier
|
||||
};
|
||||
}
|
||||
return selectedResourceAction;
|
||||
});
|
||||
|
||||
export const selectComponentResourceActionFromRoute = createSelector(selectCurrentRoute, (route) => {
|
||||
let selectedResourceAction: ComponentResourceAction | null = null;
|
||||
if (route?.params.action && route?.params.policy && route?.params.resource && route?.params.resourceIdentifier) {
|
||||
// always select the action and resource from the route
|
||||
selectedResourceAction = {
|
||||
action: route.params.action,
|
||||
policy: route.params.policy,
|
||||
resource: route.params.resource,
|
||||
resourceIdentifier: route.params.resourceIdentifier
|
||||
};
|
||||
}
|
||||
return selectedResourceAction;
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 { AccessPolicyEntity, ComponentResourceAction, PolicyStatus, ResourceAction } from '../shared';
|
||||
import { TenantEntity, UserEntity, UserGroupEntity } from '../../../../state/shared';
|
||||
|
||||
export const accessPolicyFeatureKey = 'accessPolicy';
|
||||
|
||||
export interface SetAccessPolicyRequest {
|
||||
resourceAction: ResourceAction;
|
||||
}
|
||||
|
||||
export interface SelectGlobalAccessPolicyRequest {
|
||||
resourceAction: ResourceAction;
|
||||
}
|
||||
|
||||
export interface SelectComponentAccessPolicyRequest {
|
||||
resourceAction: ComponentResourceAction;
|
||||
}
|
||||
|
||||
export interface LoadAccessPolicyRequest {
|
||||
resourceAction: ResourceAction;
|
||||
}
|
||||
|
||||
export interface AccessPolicyResponse {
|
||||
accessPolicy: AccessPolicyEntity;
|
||||
policyStatus?: PolicyStatus;
|
||||
}
|
||||
|
||||
export interface ResetAccessPolicy {
|
||||
policyStatus: PolicyStatus;
|
||||
}
|
||||
|
||||
export interface LoadAccessPolicyError {
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface RemoveTenantFromPolicyRequest {
|
||||
tenantType: 'user' | 'userGroup';
|
||||
tenant: TenantEntity;
|
||||
}
|
||||
|
||||
export interface AddTenantToPolicyDialogRequest {
|
||||
accessPolicy: AccessPolicyEntity;
|
||||
}
|
||||
|
||||
export interface AddTenantsToPolicyRequest {
|
||||
users: TenantEntity[];
|
||||
userGroups: TenantEntity[];
|
||||
}
|
||||
|
||||
export interface AccessPolicyState {
|
||||
resourceAction?: ResourceAction;
|
||||
policyStatus?: PolicyStatus;
|
||||
accessPolicy?: AccessPolicyEntity;
|
||||
saving: boolean;
|
||||
loadedTimestamp: string;
|
||||
error: string | null;
|
||||
status: 'pending' | 'loading' | 'error' | 'success';
|
||||
}
|
|
@ -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 { Action, combineReducers, createFeatureSelector } from '@ngrx/store';
|
||||
import { accessPolicyFeatureKey, AccessPolicyState } from './access-policy';
|
||||
import { accessPolicyReducer } from './access-policy/access-policy.reducer';
|
||||
import { tenantsFeatureKey, TenantsState } from './tenants';
|
||||
import { tenantsReducer } from './tenants/tenants.reducer';
|
||||
import { policyComponentFeatureKey, PolicyComponentState } from './policy-component';
|
||||
import { policyComponentReducer } from './policy-component/policy-component.reducer';
|
||||
|
||||
export const accessPoliciesFeatureKey = 'accessPolicies';
|
||||
|
||||
export interface AccessPoliciesState {
|
||||
[accessPolicyFeatureKey]: AccessPolicyState;
|
||||
[tenantsFeatureKey]: TenantsState;
|
||||
[policyComponentFeatureKey]: PolicyComponentState;
|
||||
}
|
||||
|
||||
export function reducers(state: AccessPoliciesState | undefined, action: Action) {
|
||||
return combineReducers({
|
||||
[accessPolicyFeatureKey]: accessPolicyReducer,
|
||||
[tenantsFeatureKey]: tenantsReducer,
|
||||
[policyComponentFeatureKey]: policyComponentReducer
|
||||
})(state, action);
|
||||
}
|
||||
|
||||
export const selectAccessPoliciesState = createFeatureSelector<AccessPoliciesState>(accessPoliciesFeatureKey);
|
|
@ -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.
|
||||
*/
|
||||
|
||||
import { ComponentResourceAction } from '../shared';
|
||||
|
||||
export const policyComponentFeatureKey = 'policyComponent';
|
||||
|
||||
export interface LoadPolicyComponentRequest {
|
||||
componentResourceAction: ComponentResourceAction;
|
||||
}
|
||||
|
||||
export interface LoadPolicyComponentSuccess {
|
||||
label: string;
|
||||
resource: string;
|
||||
allowRemoteAccess: boolean;
|
||||
}
|
||||
|
||||
export interface PolicyComponentState {
|
||||
label: string;
|
||||
allowRemoteAccess: boolean;
|
||||
resource: string;
|
||||
loadedTimestamp: string;
|
||||
error: string | null;
|
||||
status: 'pending' | 'loading' | 'error' | 'success';
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { LoadPolicyComponentRequest, LoadPolicyComponentSuccess } from './index';
|
||||
|
||||
const POLICY_COMPONENT_PREFIX: string = '[Policy Component]';
|
||||
|
||||
export const loadPolicyComponent = createAction(
|
||||
`${POLICY_COMPONENT_PREFIX} Load Policy Component`,
|
||||
props<{ request: LoadPolicyComponentRequest }>()
|
||||
);
|
||||
|
||||
export const loadPolicyComponentSuccess = createAction(
|
||||
`${POLICY_COMPONENT_PREFIX} Load Policy Component Success`,
|
||||
props<{ response: LoadPolicyComponentSuccess }>()
|
||||
);
|
||||
|
||||
export const policyComponentApiError = createAction(
|
||||
`${POLICY_COMPONENT_PREFIX} Policy Component Api Error`,
|
||||
props<{ error: string }>()
|
||||
);
|
||||
|
||||
export const resetPolicyComponentState = createAction(`${POLICY_COMPONENT_PREFIX} Reset Policy Component State`);
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import * as PolicyComponentActions from './policy-component.actions';
|
||||
import { catchError, from, map, of, switchMap } from 'rxjs';
|
||||
import { AccessPolicyService } from '../../service/access-policy.service';
|
||||
|
||||
@Injectable()
|
||||
export class PolicyComponentEffects {
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private accessPoliciesService: AccessPolicyService
|
||||
) {}
|
||||
|
||||
loadPolicyComponent$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(PolicyComponentActions.loadPolicyComponent),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
from(this.accessPoliciesService.getPolicyComponent(request.componentResourceAction)).pipe(
|
||||
map((response) =>
|
||||
PolicyComponentActions.loadPolicyComponentSuccess({
|
||||
response: {
|
||||
label: response.component.name,
|
||||
resource: request.componentResourceAction.resource,
|
||||
allowRemoteAccess:
|
||||
request.componentResourceAction.resource === 'input-ports' ||
|
||||
request.componentResourceAction.resource === 'output-ports'
|
||||
? response.allowRemoteAccess
|
||||
: false
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) => {
|
||||
if (error.status === 403) {
|
||||
return of(
|
||||
PolicyComponentActions.loadPolicyComponentSuccess({
|
||||
response: {
|
||||
label: request.componentResourceAction.resourceIdentifier,
|
||||
resource: request.componentResourceAction.resource,
|
||||
allowRemoteAccess: false
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return of(
|
||||
PolicyComponentActions.policyComponentApiError({
|
||||
error: error.error
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PolicyComponentState } from './index';
|
||||
import { createReducer, on } from '@ngrx/store';
|
||||
import {
|
||||
loadPolicyComponent,
|
||||
loadPolicyComponentSuccess,
|
||||
resetPolicyComponentState,
|
||||
policyComponentApiError
|
||||
} from './policy-component.actions';
|
||||
|
||||
export const initialState: PolicyComponentState = {
|
||||
label: '',
|
||||
resource: '',
|
||||
allowRemoteAccess: false,
|
||||
loadedTimestamp: '',
|
||||
error: null,
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
export const policyComponentReducer = createReducer(
|
||||
initialState,
|
||||
on(loadPolicyComponent, (state) => ({
|
||||
...state,
|
||||
status: 'loading' as const
|
||||
})),
|
||||
on(loadPolicyComponentSuccess, (state, { response }) => ({
|
||||
...state,
|
||||
label: response.label,
|
||||
resource: response.resource,
|
||||
allowRemoteAccess: response.allowRemoteAccess,
|
||||
status: 'success' as const
|
||||
})),
|
||||
on(policyComponentApiError, (state, { error }) => ({
|
||||
...state,
|
||||
error: error,
|
||||
status: 'error' as const
|
||||
})),
|
||||
on(resetPolicyComponentState, (state) => ({
|
||||
...initialState
|
||||
}))
|
||||
);
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { AccessPoliciesState, selectAccessPoliciesState } from '../index';
|
||||
import { policyComponentFeatureKey } from './index';
|
||||
|
||||
export const selectPolicyComponentState = createSelector(
|
||||
selectAccessPoliciesState,
|
||||
(state: AccessPoliciesState) => state[policyComponentFeatureKey]
|
||||
);
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { AccessPolicySummary, Permissions, Revision, TenantEntity } from '../../../../state/shared';
|
||||
|
||||
export enum PolicyStatus {
|
||||
Found = 'Found',
|
||||
Inherited = 'Inherited',
|
||||
NotFound = 'NotFound',
|
||||
Forbidden = 'Forbidden'
|
||||
}
|
||||
|
||||
export enum Action {
|
||||
Read = 'read',
|
||||
Write = 'write'
|
||||
}
|
||||
|
||||
export interface ResourceAction {
|
||||
resource: string;
|
||||
resourceIdentifier?: string;
|
||||
action: Action;
|
||||
}
|
||||
|
||||
export interface ComponentResourceAction extends ResourceAction {
|
||||
resourceIdentifier: string;
|
||||
policy: string;
|
||||
}
|
||||
|
||||
export interface AccessPolicyEntity {
|
||||
id: string;
|
||||
component: AccessPolicy;
|
||||
revision: Revision;
|
||||
uri: string;
|
||||
permissions: Permissions;
|
||||
generated: string;
|
||||
}
|
||||
|
||||
export interface AccessPolicy extends AccessPolicySummary {
|
||||
users: TenantEntity[];
|
||||
userGroups: TenantEntity[];
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { UserEntity, UserGroupEntity } from '../../../../state/shared';
|
||||
|
||||
export const tenantsFeatureKey = 'tenants';
|
||||
|
||||
export interface LoadTenantsSuccess {
|
||||
users: UserEntity[];
|
||||
userGroups: UserGroupEntity[];
|
||||
}
|
||||
|
||||
export interface TenantsState {
|
||||
users: UserEntity[];
|
||||
userGroups: UserGroupEntity[];
|
||||
loadedTimestamp: string;
|
||||
error: string | null;
|
||||
status: 'pending' | 'loading' | 'error' | 'success';
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { LoadTenantsSuccess } from './index';
|
||||
|
||||
const TENANTS_PREFIX: string = '[Tenants]';
|
||||
|
||||
export const loadTenants = createAction(`${TENANTS_PREFIX} Load Tenants`);
|
||||
|
||||
export const loadTenantsSuccess = createAction(
|
||||
`${TENANTS_PREFIX} Load Tenants Success`,
|
||||
props<{ response: LoadTenantsSuccess }>()
|
||||
);
|
||||
|
||||
export const tenantsApiError = createAction(`${TENANTS_PREFIX} Tenants Api Error`, props<{ error: string }>());
|
||||
|
||||
export const resetTenantsState = createAction(`${TENANTS_PREFIX} Reset Tenants State`);
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import * as TenantsActions from './tenants.actions';
|
||||
import { catchError, combineLatest, filter, from, map, of, switchMap, take, tap, withLatestFrom } from 'rxjs';
|
||||
import { AccessPolicyService } from '../../service/access-policy.service';
|
||||
|
||||
@Injectable()
|
||||
export class TenantsEffects {
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private accessPoliciesService: AccessPolicyService
|
||||
) {}
|
||||
|
||||
loadTenants$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(TenantsActions.loadTenants),
|
||||
switchMap(() =>
|
||||
combineLatest([this.accessPoliciesService.getUsers(), this.accessPoliciesService.getUserGroups()]).pipe(
|
||||
map(([usersResponse, userGroupsResponse]) =>
|
||||
TenantsActions.loadTenantsSuccess({
|
||||
response: {
|
||||
users: usersResponse.users,
|
||||
userGroups: userGroupsResponse.userGroups
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
TenantsActions.tenantsApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TenantsState } from './index';
|
||||
import { createReducer, on } from '@ngrx/store';
|
||||
import { loadTenants, loadTenantsSuccess, resetTenantsState, tenantsApiError } from './tenants.actions';
|
||||
|
||||
export const initialState: TenantsState = {
|
||||
users: [],
|
||||
userGroups: [],
|
||||
loadedTimestamp: '',
|
||||
error: null,
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
export const tenantsReducer = createReducer(
|
||||
initialState,
|
||||
on(loadTenants, (state) => ({
|
||||
...state,
|
||||
status: 'loading' as const
|
||||
})),
|
||||
on(loadTenantsSuccess, (state, { response }) => ({
|
||||
...state,
|
||||
users: response.users,
|
||||
userGroups: response.userGroups,
|
||||
status: 'success' as const
|
||||
})),
|
||||
on(tenantsApiError, (state, { error }) => ({
|
||||
...state,
|
||||
error: error,
|
||||
status: 'error' as const
|
||||
})),
|
||||
on(resetTenantsState, (state) => ({
|
||||
...initialState
|
||||
}))
|
||||
);
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createSelector } from '@ngrx/store';
|
||||
import { AccessPoliciesState, selectAccessPoliciesState } from '../index';
|
||||
import { tenantsFeatureKey, TenantsState } from './index';
|
||||
|
||||
export const selectTenants = createSelector(
|
||||
selectAccessPoliciesState,
|
||||
(state: AccessPoliciesState) => state[tenantsFeatureKey]
|
||||
);
|
||||
|
||||
export const selectUsers = createSelector(selectTenants, (state: TenantsState) => state.users);
|
||||
|
||||
export const selectUserGroups = createSelector(selectTenants, (state: TenantsState) => state.userGroups);
|
|
@ -0,0 +1,54 @@
|
|||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<h2 mat-dialog-title>Add Users/Groups To Policy</h2>
|
||||
<form class="add-tenant-to-policy-form" [formGroup]="addTenantsForm">
|
||||
<mat-dialog-content>
|
||||
<div *ngIf="filteredUsers.length > 0">
|
||||
<mat-label>Users</mat-label>
|
||||
<mat-selection-list formControlName="users" class="border">
|
||||
<mat-list-option *ngFor="let user of filteredUsers" togglePosition="before" [value]="user.id"
|
||||
>{{ user.component.identity }}
|
||||
</mat-list-option>
|
||||
</mat-selection-list>
|
||||
</div>
|
||||
<div *ngIf="filteredUserGroups.length > 0" [class.mt-4]="filteredUsers.length > 0">
|
||||
<mat-label>User Groups</mat-label>
|
||||
<mat-selection-list formControlName="userGroups" class="border">
|
||||
<mat-list-option
|
||||
*ngFor="let userGroup of filteredUserGroups"
|
||||
togglePosition="before"
|
||||
[value]="userGroup.id">
|
||||
<i class="fa fa-users mr-3"></i>{{ userGroup.component.identity }}
|
||||
</mat-list-option>
|
||||
</mat-selection-list>
|
||||
</div>
|
||||
<div *ngIf="filteredUsers.length === 0 && filteredUserGroups.length === 0" class="value">
|
||||
All users and groups are assigned to this policy.
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end" *ngIf="{ value: (saving$ | async)! } as saving">
|
||||
<button mat-raised-button mat-dialog-close color="accent">Cancel</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
[disabled]="addTenantsForm.invalid || saving.value"
|
||||
(click)="addClicked()"
|
||||
color="primary">
|
||||
<span *nifiSpinner="saving.value">Add</span>
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</form>
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
.add-tenant-to-policy-form {
|
||||
@include mat.button-density(-1);
|
||||
|
||||
.fa {
|
||||
color: #004849;
|
||||
}
|
||||
|
||||
mat-selection-list {
|
||||
max-height: 250px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AddTenantToPolicyDialog } from './add-tenant-to-policy-dialog.component';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { AddTenantToPolicyDialogRequest } from '../../../state/access-policy';
|
||||
|
||||
describe('AddTenantToPolicyDialog', () => {
|
||||
let component: AddTenantToPolicyDialog;
|
||||
let fixture: ComponentFixture<AddTenantToPolicyDialog>;
|
||||
|
||||
const data: AddTenantToPolicyDialogRequest = {
|
||||
accessPolicy: {
|
||||
revision: {
|
||||
clientId: '311032c3-f210-42f9-8a31-862c88b5fbd4',
|
||||
version: 4
|
||||
},
|
||||
id: 'f99bccd1-a30e-3e4a-98a2-dbc708edc67f',
|
||||
uri: 'https://localhost:4200/nifi-api/policies/f99bccd1-a30e-3e4a-98a2-dbc708edc67f',
|
||||
permissions: {
|
||||
canRead: true,
|
||||
canWrite: true
|
||||
},
|
||||
generated: '15:48:06 EST',
|
||||
component: {
|
||||
id: 'f99bccd1-a30e-3e4a-98a2-dbc708edc67f',
|
||||
resource: '/flow',
|
||||
action: 'read',
|
||||
configurable: true,
|
||||
users: [
|
||||
{
|
||||
revision: {
|
||||
version: 0
|
||||
},
|
||||
id: 'bc646be3-146f-3cf2-bfd6-3a9f687ee7ab',
|
||||
permissions: {
|
||||
canRead: true,
|
||||
canWrite: true
|
||||
},
|
||||
component: {
|
||||
id: 'bc646be3-146f-3cf2-bfd6-3a9f687ee7ab',
|
||||
identity: 'identify',
|
||||
configurable: false
|
||||
}
|
||||
}
|
||||
],
|
||||
userGroups: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AddTenantToPolicyDialog, BrowserAnimationsModule],
|
||||
providers: [{ provide: MAT_DIALOG_DATA, useValue: data }]
|
||||
});
|
||||
fixture = TestBed.createComponent(AddTenantToPolicyDialog);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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, DestroyRef, EventEmitter, inject, Inject, Input, Output } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { AccessPolicy } from '../../../state/shared';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule } 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 { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { TenantEntity, UserEntity, UserGroupEntity } from '../../../../../state/shared';
|
||||
import { AddTenantsToPolicyRequest, AddTenantToPolicyDialogRequest } from '../../../state/access-policy';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'add-tenant-to-policy-dialog',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatDialogModule,
|
||||
MatButtonModule,
|
||||
FormsModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
MatRadioModule,
|
||||
MatCheckboxModule,
|
||||
NgIf,
|
||||
AsyncPipe,
|
||||
MatListModule,
|
||||
NgForOf,
|
||||
NifiSpinnerDirective
|
||||
],
|
||||
templateUrl: './add-tenant-to-policy-dialog.component.html',
|
||||
styleUrls: ['./add-tenant-to-policy-dialog.component.scss']
|
||||
})
|
||||
export class AddTenantToPolicyDialog {
|
||||
@Input() set users$(users$: Observable<UserEntity[]>) {
|
||||
users$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((users: UserEntity[]) => {
|
||||
const policy: AccessPolicy = this.request.accessPolicy.component;
|
||||
|
||||
this.filteredUsers = users.filter((user: UserEntity) => {
|
||||
return !policy.users.some((tenant: TenantEntity) => tenant.id === user.id);
|
||||
});
|
||||
|
||||
this.userLookup.clear();
|
||||
this.filteredUsers.forEach((user: UserEntity) => {
|
||||
this.userLookup.set(user.id, user);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Input() set userGroups$(userGroups$: Observable<UserGroupEntity[]>) {
|
||||
userGroups$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((userGroups: UserGroupEntity[]) => {
|
||||
const policy: AccessPolicy = this.request.accessPolicy.component;
|
||||
|
||||
this.filteredUserGroups = userGroups.filter((userGroup: UserGroupEntity) => {
|
||||
return !policy.userGroups.some((tenant: TenantEntity) => tenant.id === userGroup.id);
|
||||
});
|
||||
|
||||
this.userGroupLookup.clear();
|
||||
this.filteredUserGroups.forEach((user: UserGroupEntity) => {
|
||||
this.userGroupLookup.set(user.id, user);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Input() saving$!: Observable<boolean>;
|
||||
|
||||
@Output() addTenants: EventEmitter<AddTenantsToPolicyRequest> = new EventEmitter<AddTenantsToPolicyRequest>();
|
||||
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
addTenantsForm: FormGroup;
|
||||
filteredUsers: UserEntity[] = [];
|
||||
filteredUserGroups: UserGroupEntity[] = [];
|
||||
|
||||
userLookup: Map<string, UserEntity> = new Map<string, UserEntity>();
|
||||
userGroupLookup: Map<string, UserGroupEntity> = new Map<string, UserGroupEntity>();
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) private request: AddTenantToPolicyDialogRequest,
|
||||
private formBuilder: FormBuilder
|
||||
) {
|
||||
this.addTenantsForm = this.formBuilder.group({
|
||||
users: new FormControl([]),
|
||||
userGroups: new FormControl([])
|
||||
});
|
||||
}
|
||||
|
||||
addClicked(): void {
|
||||
const users: TenantEntity[] = [];
|
||||
|
||||
const usersSelected: string[] = this.addTenantsForm.get('users')?.value;
|
||||
usersSelected.forEach((userId: string) => {
|
||||
const user: UserEntity | undefined = this.userLookup.get(userId);
|
||||
if (user) {
|
||||
users.push({
|
||||
id: user.id,
|
||||
revision: user.revision,
|
||||
permissions: user.permissions,
|
||||
component: {
|
||||
id: user.component.id,
|
||||
identity: user.component.identity,
|
||||
configurable: user.component.configurable
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const userGroups: TenantEntity[] = [];
|
||||
const userGroupsSelected: string[] = this.addTenantsForm.get('userGroups')?.value;
|
||||
userGroupsSelected.forEach((userGroupId: string) => {
|
||||
const userGroup: UserGroupEntity | undefined = this.userGroupLookup.get(userGroupId);
|
||||
if (userGroup) {
|
||||
userGroups.push({
|
||||
id: userGroup.id,
|
||||
revision: userGroup.revision,
|
||||
permissions: userGroup.permissions,
|
||||
component: {
|
||||
id: userGroup.component.id,
|
||||
identity: userGroup.component.identity,
|
||||
configurable: userGroup.component.configurable
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.addTenants.next({
|
||||
users,
|
||||
userGroups
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<div class="policy-table h-full relative">
|
||||
<div class="listing-table overflow-y-auto border absolute inset-0">
|
||||
<table
|
||||
mat-table
|
||||
[dataSource]="dataSource"
|
||||
matSort
|
||||
matSortDisableClear
|
||||
(matSortChange)="updateSort($event)"
|
||||
[matSortActive]="sort.active"
|
||||
[matSortDirection]="sort.direction">
|
||||
<!-- User column -->
|
||||
<ng-container matColumnDef="user">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>User</th>
|
||||
<td mat-cell *matCellDef="let item" class="items-center">
|
||||
<i *ngIf="item.tenantType === 'userGroup'" class="fa fa-users mr-3"></i>{{ item.user }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let item">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div
|
||||
class="pointer fa fa-trash"
|
||||
title="Remove"
|
||||
*ngIf="canRemove()"
|
||||
(click)="removeClicked(item)"></div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; let even = even; columns: displayedColumns"
|
||||
(click)="select(row)"
|
||||
[class.selected]="isSelected(row)"
|
||||
[class.even]="even"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,24 @@
|
|||
/*!
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.policy-table {
|
||||
.listing-table {
|
||||
.mat-column-actions {
|
||||
width: 75px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { PolicyTable } from './policy-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('PolicyTable', () => {
|
||||
let component: PolicyTable;
|
||||
let fixture: ComponentFixture<PolicyTable>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
PolicyTable,
|
||||
MatTableModule,
|
||||
MatSortModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
MatSelectModule,
|
||||
NoopAnimationsModule
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(PolicyTable);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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, MatTableModule } from '@angular/material/table';
|
||||
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||
import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
||||
import { CurrentUser } from '../../../../../state/current-user';
|
||||
import { TenantEntity, UserEntity } from '../../../../../state/shared';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { AccessPolicyEntity } from '../../../state/shared';
|
||||
import { RemoveTenantFromPolicyRequest } from '../../../state/access-policy';
|
||||
|
||||
export interface TenantItem {
|
||||
id: string;
|
||||
user: string;
|
||||
tenantType: 'user' | 'userGroup';
|
||||
configurable: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'policy-table',
|
||||
standalone: true,
|
||||
templateUrl: './policy-table.component.html',
|
||||
imports: [MatTableModule, MatSortModule, NgIf],
|
||||
styleUrls: ['./policy-table.component.scss', '../../../../../../assets/styles/listing-table.scss']
|
||||
})
|
||||
export class PolicyTable {
|
||||
displayedColumns: string[] = ['user', 'actions'];
|
||||
dataSource: MatTableDataSource<TenantItem> = new MatTableDataSource<TenantItem>();
|
||||
|
||||
tenantLookup: Map<string, TenantEntity> = new Map<string, TenantEntity>();
|
||||
|
||||
@Input() set policy(policy: AccessPolicyEntity | undefined) {
|
||||
const tenantItems: TenantItem[] = [];
|
||||
|
||||
if (policy) {
|
||||
policy.component.users.forEach((user) => {
|
||||
this.tenantLookup.set(user.id, user);
|
||||
tenantItems.push({
|
||||
id: user.id,
|
||||
tenantType: 'user',
|
||||
user: user.component.identity,
|
||||
configurable: user.component.configurable
|
||||
});
|
||||
});
|
||||
policy.component.userGroups.forEach((userGroup) => {
|
||||
this.tenantLookup.set(userGroup.id, userGroup);
|
||||
tenantItems.push({
|
||||
id: userGroup.id,
|
||||
tenantType: 'userGroup',
|
||||
user: userGroup.component.identity,
|
||||
configurable: userGroup.component.configurable
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.dataSource.data = this.sortUsers(tenantItems, this.sort);
|
||||
this._policy = policy;
|
||||
}
|
||||
|
||||
@Input() supportsPolicyModification!: boolean;
|
||||
|
||||
@Output() removeTenantFromPolicy: EventEmitter<RemoveTenantFromPolicyRequest> =
|
||||
new EventEmitter<RemoveTenantFromPolicyRequest>();
|
||||
|
||||
private _policy: AccessPolicyEntity | undefined;
|
||||
selectedTenantId: string | null = null;
|
||||
|
||||
sort: Sort = {
|
||||
active: 'user',
|
||||
direction: 'asc'
|
||||
};
|
||||
|
||||
constructor(private nifiCommon: NiFiCommon) {}
|
||||
|
||||
updateSort(sort: Sort): void {
|
||||
this.sort = sort;
|
||||
this.dataSource.data = this.sortUsers(this.dataSource.data, sort);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return retVal * (isAsc ? 1 : -1);
|
||||
});
|
||||
}
|
||||
|
||||
select(item: TenantItem): void {
|
||||
this.selectedTenantId = item.id;
|
||||
}
|
||||
|
||||
isSelected(item: TenantItem): boolean {
|
||||
if (this.selectedTenantId) {
|
||||
return item.id == this.selectedTenantId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
canRemove(): boolean {
|
||||
if (this._policy) {
|
||||
return (
|
||||
this.supportsPolicyModification &&
|
||||
this._policy.permissions.canWrite &&
|
||||
this._policy.component.configurable
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
removeClicked(item: TenantItem): void {
|
||||
const tenant: TenantEntity | undefined = this.tenantLookup.get(item.id);
|
||||
if (tenant) {
|
||||
this.removeTenantFromPolicy.next({
|
||||
tenantType: item.tenantType,
|
||||
tenant
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { ComponentAccessPolicies } from './component-access-policies.component';
|
||||
import { authorizationGuard } from '../../../../service/guard/authorization.guard';
|
||||
import { CurrentUser } from '../../../../state/current-user';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ':action/:policy/:resource/:resourceIdentifier',
|
||||
canMatch: [authorizationGuard((user: CurrentUser) => user.tenantsPermissions.canRead)],
|
||||
component: ComponentAccessPolicies
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class ComponentAccessPoliciesRoutingModule {}
|
|
@ -0,0 +1,153 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<ng-container *ngIf="accessPolicyState$ | async; let accessPolicyState">
|
||||
<div *ngIf="isInitialLoading(accessPolicyState); else loaded">
|
||||
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
||||
</div>
|
||||
|
||||
<ng-template #loaded>
|
||||
<ng-container *ngIf="policyComponentState$ | async; let policyComponentState">
|
||||
<div
|
||||
class="component-access-policies flex flex-col h-full gap-y-2"
|
||||
*ngIf="flowConfiguration$ | async; let flowConfiguration">
|
||||
<div class="value">
|
||||
<div class="mb-2" [ngSwitch]="accessPolicyState.policyStatus">
|
||||
<ng-container *ngSwitchCase="PolicyStatus.NotFound">
|
||||
No policy for the specified resource.
|
||||
<ng-container *ngIf="flowConfiguration.supportsConfigurableAuthorizer">
|
||||
<a (click)="createNewPolicy()">Create</a> a new policy.
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PolicyStatus.Inherited">
|
||||
<ng-container *ngIf="accessPolicyState.accessPolicy">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
getTemplateForInheritedPolicy(accessPolicyState.accessPolicy);
|
||||
context: {
|
||||
$implicit: accessPolicyState.accessPolicy,
|
||||
supportsConfigurableAuthorizer:
|
||||
flowConfiguration.supportsConfigurableAuthorizer
|
||||
}
|
||||
"></ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PolicyStatus.Forbidden">
|
||||
Not authorized to access the policy for the specified resource.
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<form [formGroup]="policyForm">
|
||||
<div class="flex gap-x-2">
|
||||
<div class="flex gap-x-1 -mt-2">
|
||||
<div class="operation-context-logo flex flex-col">
|
||||
<i class="icon" [class]="getContextIcon()"></i>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="operation-context-name">{{ policyComponentState.label }}</div>
|
||||
<div class="operation-context-type">{{ getContextType() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="policy-select">
|
||||
<mat-form-field>
|
||||
<mat-label>Policy</mat-label>
|
||||
<mat-select
|
||||
formControlName="policyAction"
|
||||
(selectionChange)="policyActionChanged($event.value)">
|
||||
<ng-container *ngFor="let option of policyActionOptions">
|
||||
<mat-option
|
||||
*ngIf="isComponentPolicy(option, policyComponentState)"
|
||||
[value]="option.value"
|
||||
nifiTooltip
|
||||
[tooltipComponentType]="TextTip"
|
||||
[tooltipInputData]="getSelectOptionTipData(option)"
|
||||
[delayClose]="false"
|
||||
>{{ option.text }}
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div *ngIf="flowConfiguration.supportsConfigurableAuthorizer" class="flex gap-x-2">
|
||||
<button
|
||||
class="nifi-button"
|
||||
title="Add users/groups to this policy"
|
||||
[disabled]="accessPolicyState.policyStatus !== PolicyStatus.Found"
|
||||
(click)="addTenantToPolicy()">
|
||||
<i class="fa fa-user-plus"></i>
|
||||
</button>
|
||||
<button
|
||||
class="nifi-button"
|
||||
title="Delete this policy"
|
||||
[disabled]="accessPolicyState.policyStatus !== PolicyStatus.Found"
|
||||
(click)="deletePolicy()">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 -mt-2" *ngIf="currentUser$ | async as user">
|
||||
<policy-table
|
||||
[policy]="accessPolicyState.accessPolicy"
|
||||
[supportsPolicyModification]="
|
||||
flowConfiguration.supportsConfigurableAuthorizer &&
|
||||
accessPolicyState.policyStatus === PolicyStatus.Found
|
||||
"
|
||||
(removeTenantFromPolicy)="removeTenantFromPolicy($event)"></policy-table>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<div class="refresh-container flex items-center gap-x-2">
|
||||
<button class="nifi-button" (click)="refreshGlobalAccessPolicy()">
|
||||
<i class="fa fa-refresh" [class.fa-spin]="accessPolicyState.status === 'loading'"></i>
|
||||
</button>
|
||||
<div>Last updated:</div>
|
||||
<div class="refresh-timestamp">{{ accessPolicyState.loadedTimestamp }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #inheritedFromPolicies let-policy let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
|
||||
No component specific administrators.
|
||||
<ng-container *ngIf="supportsConfigurableAuthorizer">
|
||||
<a (click)="createNewPolicy()">Add</a> policy for additional administrators.
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #inheritedFromController let-policy let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
|
||||
Showing effective policy inherited from the controller.
|
||||
<ng-container *ngIf="supportsConfigurableAuthorizer">
|
||||
<a (click)="createNewPolicy()">Override</a> this policy.
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template
|
||||
#inheritedFromGlobalParameterContexts
|
||||
let-policy
|
||||
let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
|
||||
Showing effective policy inherited from global parameter context policy.
|
||||
<ng-container *ngIf="supportsConfigurableAuthorizer">
|
||||
<a (click)="createNewPolicy()">Override</a> this policy.
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #inheritedFromProcessGroup let-policy let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
|
||||
Showing effective policy inherited from <a [routerLink]="getInheritedProcessGroupRoute(policy)">Process Group</a>.
|
||||
<ng-container *ngIf="supportsConfigurableAuthorizer">
|
||||
<a (click)="createNewPolicy()">Override</a> this policy.
|
||||
</ng-container>
|
||||
</ng-template>
|
|
@ -0,0 +1,48 @@
|
|||
/*!
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.component-access-policies {
|
||||
a {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.policy-select {
|
||||
.mat-mdc-form-field {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.operation-context-logo {
|
||||
.icon {
|
||||
font-size: 36px;
|
||||
color: #ad9897;
|
||||
}
|
||||
}
|
||||
|
||||
.operation-context-name {
|
||||
font-size: 18px;
|
||||
color: #262626;
|
||||
width: 370px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.operation-context-type {
|
||||
color: #728e9b;
|
||||
}
|
||||
}
|
|
@ -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 { ComponentAccessPolicies } from './component-access-policies.component';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../state/access-policy/access-policy.reducer';
|
||||
|
||||
describe('ComponentAccessPolicies', () => {
|
||||
let component: ComponentAccessPolicies;
|
||||
let fixture: ComponentFixture<ComponentAccessPolicies>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ComponentAccessPolicies],
|
||||
providers: [provideMockStore({ initialState })]
|
||||
});
|
||||
fixture = TestBed.createComponent(ComponentAccessPolicies);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,444 @@
|
|||
/*
|
||||
* 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, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
|
||||
import {
|
||||
createAccessPolicy,
|
||||
openAddTenantToPolicyDialog,
|
||||
promptDeleteAccessPolicy,
|
||||
promptRemoveTenantFromPolicy,
|
||||
reloadAccessPolicy,
|
||||
resetAccessPolicyState,
|
||||
selectComponentAccessPolicy,
|
||||
setAccessPolicy
|
||||
} from '../../state/access-policy/access-policy.actions';
|
||||
import { AccessPolicyState, RemoveTenantFromPolicyRequest } from '../../state/access-policy';
|
||||
import { initialState } from '../../state/access-policy/access-policy.reducer';
|
||||
import {
|
||||
selectAccessPolicyState,
|
||||
selectComponentResourceActionFromRoute
|
||||
} from '../../state/access-policy/access-policy.selectors';
|
||||
import { distinctUntilChanged, filter } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { NiFiCommon } from '../../../../service/nifi-common.service';
|
||||
import { ComponentType, SelectOption, TextTipInput } from '../../../../state/shared';
|
||||
import { TextTip } from '../../../../ui/common/tooltips/text-tip/text-tip.component';
|
||||
import { AccessPolicyEntity, Action, ComponentResourceAction, PolicyStatus, ResourceAction } from '../../state/shared';
|
||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import { loadTenants, resetTenantsState } from '../../state/tenants/tenants.actions';
|
||||
import { loadPolicyComponent, resetPolicyComponentState } from '../../state/policy-component/policy-component.actions';
|
||||
import { selectPolicyComponentState } from '../../state/policy-component/policy-component.selectors';
|
||||
import { PolicyComponentState } from '../../state/policy-component';
|
||||
|
||||
@Component({
|
||||
selector: 'global-access-policies',
|
||||
templateUrl: './component-access-policies.component.html',
|
||||
styleUrls: ['./component-access-policies.component.scss']
|
||||
})
|
||||
export class ComponentAccessPolicies implements OnInit, OnDestroy {
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration);
|
||||
accessPolicyState$ = this.store.select(selectAccessPolicyState);
|
||||
policyComponentState$ = this.store.select(selectPolicyComponentState);
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
|
||||
protected readonly TextTip = TextTip;
|
||||
protected readonly Action = Action;
|
||||
protected readonly PolicyStatus = PolicyStatus;
|
||||
protected readonly ComponentType = ComponentType;
|
||||
|
||||
policyForm: FormGroup;
|
||||
policyActionOptions: SelectOption[] = [
|
||||
{
|
||||
text: 'view the component',
|
||||
value: 'read-component',
|
||||
description: 'Allows users to view component configuration details'
|
||||
},
|
||||
{
|
||||
text: 'modify the component',
|
||||
value: 'write-component',
|
||||
description: 'Allows users to modify component configuration details'
|
||||
},
|
||||
{
|
||||
text: 'operate the component',
|
||||
value: 'write-operation',
|
||||
description:
|
||||
'Allows users to operate components by changing component run status (start/stop/enable/disable), remote port transmission status, or terminating processor threads'
|
||||
},
|
||||
{
|
||||
text: 'view provenance',
|
||||
value: 'read-provenance-data',
|
||||
description: 'Allows users to view provenance events generated by this component'
|
||||
},
|
||||
{
|
||||
text: 'view the data',
|
||||
value: 'read-data',
|
||||
description:
|
||||
'Allows users to view metadata and content for this component in flowfile queues in outbound connections and through provenance events'
|
||||
},
|
||||
{
|
||||
text: 'modify the data',
|
||||
value: 'write-data',
|
||||
description:
|
||||
'Allows users to empty flowfile queues in outbound connections and submit replays through provenance events'
|
||||
},
|
||||
{
|
||||
text: 'receive data via site-to-site',
|
||||
value: 'write-receive-data',
|
||||
description: 'Allows this port to receive data from these NiFi instances',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
text: 'send data via site-to-site',
|
||||
value: 'write-send-data',
|
||||
description: 'Allows this port to send data to these NiFi instances',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
text: 'view the policies',
|
||||
value: 'read-policies',
|
||||
description: 'Allows users to view the list of users who can view/modify this component'
|
||||
},
|
||||
{
|
||||
text: 'modify the policies',
|
||||
value: 'write-policies',
|
||||
description: 'Allows users to modify the list of users who can view/modify this component'
|
||||
}
|
||||
];
|
||||
|
||||
action!: Action;
|
||||
resource!: string;
|
||||
policy!: string;
|
||||
resourceIdentifier!: string;
|
||||
|
||||
@ViewChild('inheritedFromPolicies') inheritedFromPolicies!: TemplateRef<any>;
|
||||
@ViewChild('inheritedFromController') inheritedFromController!: TemplateRef<any>;
|
||||
@ViewChild('inheritedFromGlobalParameterContexts') inheritedFromGlobalParameterContexts!: TemplateRef<any>;
|
||||
@ViewChild('inheritedFromProcessGroup') inheritedFromProcessGroup!: TemplateRef<any>;
|
||||
|
||||
constructor(
|
||||
private store: Store<AccessPolicyState>,
|
||||
private formBuilder: FormBuilder,
|
||||
private nifiCommon: NiFiCommon
|
||||
) {
|
||||
this.policyForm = this.formBuilder.group({
|
||||
policyAction: new FormControl(this.policyActionOptions[0].value, Validators.required)
|
||||
});
|
||||
|
||||
this.store
|
||||
.select(selectComponentResourceActionFromRoute)
|
||||
.pipe(
|
||||
filter((resourceAction) => resourceAction != null),
|
||||
distinctUntilChanged((a, b) => {
|
||||
// @ts-ignore
|
||||
const aResourceAction: ComponentResourceAction = a;
|
||||
// @ts-ignore
|
||||
const bResourceAction: ComponentResourceAction = b;
|
||||
|
||||
return (
|
||||
aResourceAction.action == bResourceAction.action &&
|
||||
aResourceAction.policy == bResourceAction.policy &&
|
||||
aResourceAction.resource == bResourceAction.resource &&
|
||||
aResourceAction.resourceIdentifier == bResourceAction.resourceIdentifier
|
||||
);
|
||||
}),
|
||||
takeUntilDestroyed()
|
||||
)
|
||||
.subscribe((componentResourceAction) => {
|
||||
if (componentResourceAction) {
|
||||
this.action = componentResourceAction.action;
|
||||
this.policy = componentResourceAction.policy;
|
||||
this.resource = componentResourceAction.resource;
|
||||
this.resourceIdentifier = componentResourceAction.resourceIdentifier;
|
||||
|
||||
// data transfer policies for site to site are presented different in the form so
|
||||
// we need to distinguish by type
|
||||
let policyForResource: string = this.policy;
|
||||
if (this.policy === 'data-transfer') {
|
||||
if (this.resource === 'input-ports') {
|
||||
policyForResource = 'receive-data';
|
||||
} else {
|
||||
policyForResource = 'send-data';
|
||||
}
|
||||
}
|
||||
|
||||
this.policyForm.get('policyAction')?.setValue(`${this.action}-${policyForResource}`);
|
||||
|
||||
// component policies are presented simply as '/processors/1234' while non-component policies
|
||||
// like viewing provenance for a specific component is presented as `/provenance-data/processors/1234`
|
||||
let resourceToLoad: string = this.resource;
|
||||
if (componentResourceAction.policy !== 'component') {
|
||||
resourceToLoad = `${this.policy}/${this.resource}`;
|
||||
}
|
||||
|
||||
const resourceAction: ResourceAction = {
|
||||
action: this.action,
|
||||
resource: resourceToLoad,
|
||||
resourceIdentifier: this.resourceIdentifier
|
||||
};
|
||||
|
||||
this.store.dispatch(
|
||||
loadPolicyComponent({
|
||||
request: {
|
||||
componentResourceAction
|
||||
}
|
||||
})
|
||||
);
|
||||
this.store.dispatch(
|
||||
setAccessPolicy({
|
||||
request: {
|
||||
resourceAction
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(loadFlowConfiguration());
|
||||
this.store.dispatch(loadTenants());
|
||||
}
|
||||
|
||||
isInitialLoading(state: AccessPolicyState): boolean {
|
||||
return state.loadedTimestamp == initialState.loadedTimestamp;
|
||||
}
|
||||
|
||||
isComponentPolicy(option: SelectOption, policyComponentState: PolicyComponentState): boolean {
|
||||
// consider the type of component to override which policies shouldn't be supported
|
||||
|
||||
if (policyComponentState.resource === 'process-groups') {
|
||||
switch (option.value) {
|
||||
case 'write-send-data':
|
||||
case 'write-receive-data':
|
||||
return false;
|
||||
}
|
||||
} else if (
|
||||
policyComponentState.resource === 'controller-services' ||
|
||||
policyComponentState.resource === 'reporting-tasks'
|
||||
) {
|
||||
switch (option.value) {
|
||||
case 'read-data':
|
||||
case 'write-data':
|
||||
case 'write-send-data':
|
||||
case 'write-receive-data':
|
||||
case 'read-provenance-data':
|
||||
return false;
|
||||
}
|
||||
} else if (
|
||||
policyComponentState.resource === 'parameter-contexts' ||
|
||||
policyComponentState.resource === 'parameter-providers'
|
||||
) {
|
||||
switch (option.value) {
|
||||
case 'read-data':
|
||||
case 'write-data':
|
||||
case 'write-send-data':
|
||||
case 'write-receive-data':
|
||||
case 'read-provenance-data':
|
||||
case 'write-operation':
|
||||
return false;
|
||||
}
|
||||
} else if (policyComponentState.resource === 'labels') {
|
||||
switch (option.value) {
|
||||
case 'write-operation':
|
||||
case 'read-data':
|
||||
case 'write-data':
|
||||
case 'write-send-data':
|
||||
case 'write-receive-data':
|
||||
return false;
|
||||
}
|
||||
} else if (policyComponentState.resource === 'input-ports' && policyComponentState.allowRemoteAccess) {
|
||||
// if input ports allow remote access, disable send data. if input ports do not allow remote
|
||||
// access it will fall through to the else block where both send and receive data will be disabled
|
||||
switch (option.value) {
|
||||
case 'write-send-data':
|
||||
return false;
|
||||
}
|
||||
} else if (policyComponentState.resource === 'output-ports' && policyComponentState.allowRemoteAccess) {
|
||||
// if output ports allow remote access, disable receive data. if output ports do not allow remote
|
||||
// access it will fall through to the else block where both send and receive data will be disabled
|
||||
switch (option.value) {
|
||||
case 'write-receive-data':
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
switch (option.value) {
|
||||
case 'write-send-data':
|
||||
case 'write-receive-data':
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// enable all other options
|
||||
return true;
|
||||
}
|
||||
|
||||
getSelectOptionTipData(option: SelectOption): TextTipInput {
|
||||
return {
|
||||
// @ts-ignore
|
||||
text: option.description
|
||||
};
|
||||
}
|
||||
|
||||
getContextIcon(): string {
|
||||
switch (this.resource) {
|
||||
case 'processors':
|
||||
return 'icon-processor';
|
||||
case 'input-ports':
|
||||
return 'icon-port-in';
|
||||
case 'output-ports':
|
||||
return 'icon-port-out';
|
||||
case 'funnels':
|
||||
return 'icon-funnel';
|
||||
case 'labels':
|
||||
return 'icon-label';
|
||||
case 'remote-process-groups':
|
||||
return 'icon-group-remote';
|
||||
case 'parameter-contexts':
|
||||
return 'icon-drop';
|
||||
}
|
||||
|
||||
return 'icon-group';
|
||||
}
|
||||
|
||||
getContextType(): string {
|
||||
switch (this.resource) {
|
||||
case 'processors':
|
||||
return 'Processor';
|
||||
case 'input-ports':
|
||||
return 'Input Ports';
|
||||
case 'output-ports':
|
||||
return 'Output Ports';
|
||||
case 'funnels':
|
||||
return 'Funnel';
|
||||
case 'labels':
|
||||
return 'Label';
|
||||
case 'remote-process-groups':
|
||||
return 'Remote Process Group';
|
||||
case 'parameter-contexts':
|
||||
return 'Parameter Contexts';
|
||||
}
|
||||
|
||||
return 'Process Group';
|
||||
}
|
||||
|
||||
policyActionChanged(value: string): void {
|
||||
let action: Action;
|
||||
let policy: string;
|
||||
|
||||
switch (value) {
|
||||
case 'read-component':
|
||||
action = Action.Read;
|
||||
policy = 'component';
|
||||
break;
|
||||
case 'write-component':
|
||||
action = Action.Write;
|
||||
policy = 'component';
|
||||
break;
|
||||
case 'write-operation':
|
||||
action = Action.Write;
|
||||
policy = 'operation';
|
||||
break;
|
||||
case 'read-data':
|
||||
action = Action.Read;
|
||||
policy = 'data';
|
||||
break;
|
||||
case 'write-data':
|
||||
action = Action.Write;
|
||||
policy = 'data';
|
||||
break;
|
||||
case 'read-provenance-data':
|
||||
action = Action.Read;
|
||||
policy = 'provenance-data';
|
||||
break;
|
||||
case 'read-policies':
|
||||
action = Action.Read;
|
||||
policy = 'policies';
|
||||
break;
|
||||
case 'write-policies':
|
||||
action = Action.Write;
|
||||
policy = 'policies';
|
||||
break;
|
||||
default:
|
||||
action = Action.Write;
|
||||
policy = 'data-transfer';
|
||||
break;
|
||||
}
|
||||
|
||||
this.store.dispatch(
|
||||
selectComponentAccessPolicy({
|
||||
request: {
|
||||
resourceAction: {
|
||||
action,
|
||||
policy,
|
||||
resource: this.resource,
|
||||
resourceIdentifier: this.resourceIdentifier
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getTemplateForInheritedPolicy(policy: AccessPolicyEntity): TemplateRef<any> {
|
||||
if (policy.component.resource.startsWith('/policies')) {
|
||||
return this.inheritedFromPolicies;
|
||||
} else if (policy.component.resource === '/controller') {
|
||||
return this.inheritedFromController;
|
||||
} else if (policy.component.resource === '/parameter-contexts') {
|
||||
return this.inheritedFromGlobalParameterContexts;
|
||||
}
|
||||
|
||||
return this.inheritedFromProcessGroup;
|
||||
}
|
||||
|
||||
getInheritedProcessGroupRoute(policy: AccessPolicyEntity): string[] {
|
||||
return ['/process-groups', this.nifiCommon.substringAfterLast(policy.component.resource, '/')];
|
||||
}
|
||||
|
||||
createNewPolicy(): void {
|
||||
this.store.dispatch(createAccessPolicy());
|
||||
}
|
||||
|
||||
removeTenantFromPolicy(request: RemoveTenantFromPolicyRequest): void {
|
||||
this.store.dispatch(
|
||||
promptRemoveTenantFromPolicy({
|
||||
request
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
addTenantToPolicy(): void {
|
||||
this.store.dispatch(openAddTenantToPolicyDialog());
|
||||
}
|
||||
|
||||
deletePolicy(): void {
|
||||
this.store.dispatch(promptDeleteAccessPolicy());
|
||||
}
|
||||
|
||||
refreshGlobalAccessPolicy(): void {
|
||||
this.store.dispatch(reloadAccessPolicy());
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.store.dispatch(resetAccessPolicyState());
|
||||
this.store.dispatch(resetTenantsState());
|
||||
this.store.dispatch(resetPolicyComponentState());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { ComponentAccessPolicies } from './component-access-policies.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 { ComponentAccessPoliciesRoutingModule } from './component-access-policies-routing.module';
|
||||
import { NifiTooltipDirective } from '../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||
import { PolicyTable } from '../common/policy-table/policy-table.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ComponentAccessPolicies],
|
||||
exports: [ComponentAccessPolicies],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ComponentAccessPoliciesRoutingModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
MatTableModule,
|
||||
MatSortModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
MatSelectModule,
|
||||
NifiTooltipDirective,
|
||||
PolicyTable
|
||||
]
|
||||
})
|
||||
export class ComponentAccessPoliciesModule {}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { GlobalAccessPolicies } from './global-access-policies.component';
|
||||
import { authorizationGuard } from '../../../../service/guard/authorization.guard';
|
||||
import { CurrentUser } from '../../../../state/current-user';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ':action/:resource',
|
||||
canMatch: [
|
||||
authorizationGuard(
|
||||
(user: CurrentUser) =>
|
||||
user.tenantsPermissions.canRead &&
|
||||
user.policiesPermissions.canRead &&
|
||||
user.policiesPermissions.canWrite
|
||||
)
|
||||
],
|
||||
component: GlobalAccessPolicies,
|
||||
children: [
|
||||
{
|
||||
path: ':resourceIdentifier',
|
||||
component: GlobalAccessPolicies
|
||||
}
|
||||
]
|
||||
},
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'read/flow' }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class GlobalAccessPoliciesRoutingModule {}
|
|
@ -0,0 +1,160 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<ng-container *ngIf="accessPolicyState$ | async; let accessPolicyState">
|
||||
<div *ngIf="isInitialLoading(accessPolicyState); else loaded">
|
||||
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
||||
</div>
|
||||
|
||||
<ng-template #loaded>
|
||||
<div
|
||||
class="global-access-policies flex flex-col h-full gap-y-2"
|
||||
*ngIf="flowConfiguration$ | async; let flowConfiguration">
|
||||
<div class="value">
|
||||
<div class="mb-2" [ngSwitch]="accessPolicyState.policyStatus">
|
||||
<ng-container *ngSwitchCase="PolicyStatus.NotFound">
|
||||
No policy for the specified resource.
|
||||
<ng-container *ngIf="flowConfiguration.supportsConfigurableAuthorizer">
|
||||
<a (click)="createNewPolicy()">Create</a> a new policy.
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PolicyStatus.Inherited">
|
||||
<ng-container *ngIf="accessPolicyState.accessPolicy">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
getTemplateForInheritedPolicy(accessPolicyState.accessPolicy);
|
||||
context: {
|
||||
$implicit: accessPolicyState.accessPolicy,
|
||||
supportsConfigurableAuthorizer: flowConfiguration.supportsConfigurableAuthorizer
|
||||
}
|
||||
"></ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PolicyStatus.Forbidden">
|
||||
Not authorized to access the policy for the specified resource.
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<form [formGroup]="policyForm">
|
||||
<div class="flex gap-x-2">
|
||||
<div class="resource-select">
|
||||
<mat-form-field>
|
||||
<mat-label>Policy</mat-label>
|
||||
<mat-select
|
||||
formControlName="resource"
|
||||
(selectionChange)="resourceChanged($event.value)">
|
||||
<mat-option
|
||||
*ngFor="let option of resourceOptions"
|
||||
[value]="option.value"
|
||||
nifiTooltip
|
||||
[tooltipComponentType]="TextTip"
|
||||
[tooltipInputData]="getSelectOptionTipData(option)"
|
||||
[delayClose]="false"
|
||||
>{{ option.text }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="resource-identifier-select" *ngIf="supportsResourceIdentifier">
|
||||
<mat-form-field>
|
||||
<mat-label>Option</mat-label>
|
||||
<mat-select
|
||||
formControlName="resourceIdentifier"
|
||||
(selectionChange)="resourceIdentifierChanged()">
|
||||
<mat-option
|
||||
*ngFor="let option of requiredPermissionOptions"
|
||||
[value]="option.value"
|
||||
nifiTooltip
|
||||
[tooltipComponentType]="TextTip"
|
||||
[tooltipInputData]="getSelectOptionTipData(option)"
|
||||
[delayClose]="false"
|
||||
>{{ option.text }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="action-select" [class.hidden]="!supportsReadWriteAction">
|
||||
<mat-form-field>
|
||||
<mat-label>Action</mat-label>
|
||||
<mat-select formControlName="action" (selectionChange)="actionChanged()">
|
||||
<mat-option [value]="Action.Read">view</mat-option>
|
||||
<mat-option [value]="Action.Write">modify</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div *ngIf="flowConfiguration.supportsConfigurableAuthorizer" class="flex gap-x-2 items-center">
|
||||
<button
|
||||
class="nifi-button"
|
||||
title="Add users/groups to this policy"
|
||||
[disabled]="accessPolicyState.policyStatus !== PolicyStatus.Found"
|
||||
(click)="addTenantToPolicy()">
|
||||
<i class="fa fa-user-plus"></i>
|
||||
</button>
|
||||
<button
|
||||
class="nifi-button"
|
||||
title="Delete this policy"
|
||||
[disabled]="accessPolicyState.policyStatus !== PolicyStatus.Found"
|
||||
(click)="deletePolicy()">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 -mt-2" *ngIf="currentUser$ | async as user">
|
||||
<policy-table
|
||||
[policy]="accessPolicyState.accessPolicy"
|
||||
[supportsPolicyModification]="
|
||||
flowConfiguration.supportsConfigurableAuthorizer &&
|
||||
accessPolicyState.policyStatus === PolicyStatus.Found
|
||||
"
|
||||
(removeTenantFromPolicy)="removeTenantFromPolicy($event)"></policy-table>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<div class="refresh-container flex items-center gap-x-2">
|
||||
<button class="nifi-button" (click)="refreshGlobalAccessPolicy()">
|
||||
<i class="fa fa-refresh" [class.fa-spin]="accessPolicyState.status === 'loading'"></i>
|
||||
</button>
|
||||
<div>Last updated:</div>
|
||||
<div class="refresh-timestamp">{{ accessPolicyState.loadedTimestamp }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #inheritedFromPolicies let-policy let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
|
||||
Showing effective policy inherited from all policies.
|
||||
<ng-container *ngIf="supportsConfigurableAuthorizer">
|
||||
<a (click)="createNewPolicy()">Override</a> this policy.
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #inheritedFromController let-policy let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
|
||||
Showing effective policy inherited from the controller.
|
||||
<ng-container *ngIf="supportsConfigurableAuthorizer">
|
||||
<a (click)="createNewPolicy()">Override</a> this policy.
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template
|
||||
#inheritedFromNoRestrictions
|
||||
let-policy
|
||||
let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
|
||||
No restriction specific users.
|
||||
<ng-container *ngIf="supportsConfigurableAuthorizer">
|
||||
<a (click)="createNewPolicy()">Create</a> a new policy.
|
||||
</ng-container>
|
||||
</ng-template>
|
|
@ -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.
|
||||
*/
|
||||
|
||||
.global-access-policies {
|
||||
a {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.resource-select {
|
||||
.mat-mdc-form-field {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-identifier-select {
|
||||
.mat-mdc-form-field {
|
||||
width: 375px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-select {
|
||||
.mat-mdc-form-field {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 { GlobalAccessPolicies } from './global-access-policies.component';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../state/access-policy/access-policy.reducer';
|
||||
|
||||
describe('GlobalAccessPolicies', () => {
|
||||
let component: GlobalAccessPolicies;
|
||||
let fixture: ComponentFixture<GlobalAccessPolicies>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GlobalAccessPolicies],
|
||||
providers: [provideMockStore({ initialState })]
|
||||
});
|
||||
fixture = TestBed.createComponent(GlobalAccessPolicies);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* 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, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
|
||||
import {
|
||||
createAccessPolicy,
|
||||
openAddTenantToPolicyDialog,
|
||||
promptDeleteAccessPolicy,
|
||||
promptRemoveTenantFromPolicy,
|
||||
reloadAccessPolicy,
|
||||
resetAccessPolicyState,
|
||||
selectGlobalAccessPolicy,
|
||||
setAccessPolicy
|
||||
} from '../../state/access-policy/access-policy.actions';
|
||||
import { AccessPolicyState, RemoveTenantFromPolicyRequest } from '../../state/access-policy';
|
||||
import { initialState } from '../../state/access-policy/access-policy.reducer';
|
||||
import {
|
||||
selectAccessPolicyState,
|
||||
selectGlobalResourceActionFromRoute
|
||||
} from '../../state/access-policy/access-policy.selectors';
|
||||
import { distinctUntilChanged, filter } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { NiFiCommon } from '../../../../service/nifi-common.service';
|
||||
import { ComponentType, RequiredPermission, SelectOption, TextTipInput } from '../../../../state/shared';
|
||||
import { TextTip } from '../../../../ui/common/tooltips/text-tip/text-tip.component';
|
||||
import { AccessPolicyEntity, Action, PolicyStatus, ResourceAction } from '../../state/shared';
|
||||
import { loadExtensionTypesForPolicies } from '../../../../state/extension-types/extension-types.actions';
|
||||
import { selectRequiredPermissions } from '../../../../state/extension-types/extension-types.selectors';
|
||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import { AccessPoliciesState } from '../../state';
|
||||
import { loadTenants, resetTenantsState } from '../../state/tenants/tenants.actions';
|
||||
import { loadCurrentUser } from '../../../../state/current-user/current-user.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'global-access-policies',
|
||||
templateUrl: './global-access-policies.component.html',
|
||||
styleUrls: ['./global-access-policies.component.scss']
|
||||
})
|
||||
export class GlobalAccessPolicies implements OnInit, OnDestroy {
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration);
|
||||
accessPolicyState$ = this.store.select(selectAccessPolicyState);
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
|
||||
protected readonly TextTip = TextTip;
|
||||
protected readonly Action = Action;
|
||||
protected readonly PolicyStatus = PolicyStatus;
|
||||
protected readonly ComponentType = ComponentType;
|
||||
|
||||
policyForm: FormGroup;
|
||||
resourceOptions: SelectOption[];
|
||||
requiredPermissionOptions!: SelectOption[];
|
||||
supportsReadWriteAction: boolean = false;
|
||||
supportsResourceIdentifier: boolean = false;
|
||||
|
||||
@ViewChild('inheritedFromPolicies') inheritedFromPolicies!: TemplateRef<any>;
|
||||
@ViewChild('inheritedFromController') inheritedFromController!: TemplateRef<any>;
|
||||
@ViewChild('inheritedFromNoRestrictions') inheritedFromNoRestrictions!: TemplateRef<any>;
|
||||
|
||||
constructor(
|
||||
private store: Store<AccessPoliciesState>,
|
||||
private formBuilder: FormBuilder,
|
||||
private nifiCommon: NiFiCommon
|
||||
) {
|
||||
this.resourceOptions = this.nifiCommon.getAllPolicyTypeListing();
|
||||
|
||||
this.policyForm = this.formBuilder.group({
|
||||
resource: new FormControl(null, Validators.required),
|
||||
action: new FormControl(null, Validators.required)
|
||||
});
|
||||
|
||||
this.store
|
||||
.select(selectRequiredPermissions)
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe((requiredPermissions: RequiredPermission[]) => {
|
||||
const regardlessOfRestrictions: string = 'regardless of restrictions';
|
||||
|
||||
const options: SelectOption[] = [
|
||||
{
|
||||
text: regardlessOfRestrictions,
|
||||
value: '',
|
||||
description:
|
||||
'Allows users to create/modify all restricted components regardless of restrictions.'
|
||||
}
|
||||
];
|
||||
|
||||
options.push(
|
||||
...requiredPermissions.map((requiredPermission) => ({
|
||||
text: "requiring '" + requiredPermission.label + "'",
|
||||
value: requiredPermission.id,
|
||||
description:
|
||||
"Allows users to create/modify restricted components requiring '" +
|
||||
requiredPermission.label +
|
||||
"'"
|
||||
}))
|
||||
);
|
||||
|
||||
this.requiredPermissionOptions = options.sort((a: SelectOption, b: SelectOption): number => {
|
||||
if (a.text === regardlessOfRestrictions) {
|
||||
return -1;
|
||||
} else if (b.text === regardlessOfRestrictions) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return this.nifiCommon.compareString(a.text, b.text);
|
||||
});
|
||||
});
|
||||
|
||||
this.store
|
||||
.select(selectGlobalResourceActionFromRoute)
|
||||
.pipe(
|
||||
filter((resourceAction) => resourceAction != null),
|
||||
distinctUntilChanged((aResourceAction, bResourceAction) => {
|
||||
// @ts-ignore
|
||||
const a: ResourceAction = aResourceAction;
|
||||
// @ts-ignore
|
||||
const b: ResourceAction = bResourceAction;
|
||||
|
||||
return (
|
||||
a.action == b.action && a.resource == b.resource && a.resourceIdentifier == b.resourceIdentifier
|
||||
);
|
||||
}),
|
||||
takeUntilDestroyed()
|
||||
)
|
||||
.subscribe((resourceAction) => {
|
||||
if (resourceAction) {
|
||||
this.supportsReadWriteAction = this.globalPolicySupportsReadWrite(resourceAction.resource);
|
||||
|
||||
this.policyForm.get('resource')?.setValue(resourceAction.resource);
|
||||
this.policyForm.get('action')?.setValue(resourceAction.action);
|
||||
|
||||
this.updateResourceIdentifierVisibility(resourceAction.resource);
|
||||
|
||||
if (resourceAction.resource === 'restricted-components' && resourceAction.resourceIdentifier) {
|
||||
this.policyForm.get('resourceIdentifier')?.setValue(resourceAction.resourceIdentifier);
|
||||
}
|
||||
|
||||
this.store.dispatch(
|
||||
setAccessPolicy({
|
||||
request: {
|
||||
resourceAction
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(loadFlowConfiguration());
|
||||
this.store.dispatch(loadTenants());
|
||||
this.store.dispatch(loadExtensionTypesForPolicies());
|
||||
}
|
||||
|
||||
isInitialLoading(state: AccessPolicyState): boolean {
|
||||
return state.loadedTimestamp == initialState.loadedTimestamp;
|
||||
}
|
||||
|
||||
getSelectOptionTipData(option: SelectOption): TextTipInput {
|
||||
return {
|
||||
// @ts-ignore
|
||||
text: option.description
|
||||
};
|
||||
}
|
||||
|
||||
resourceChanged(value: string): void {
|
||||
if (this.globalPolicySupportsReadWrite(value)) {
|
||||
this.supportsReadWriteAction = true;
|
||||
this.supportsResourceIdentifier = false;
|
||||
|
||||
// reset the action
|
||||
this.policyForm.get('action')?.setValue(Action.Read);
|
||||
} else {
|
||||
this.supportsReadWriteAction = false;
|
||||
|
||||
// since this resource does not support read and write, update the form with the appropriate action this resource does support
|
||||
this.policyForm.get('action')?.setValue(this.globalPolicySupportsWrite(value) ? Action.Write : Action.Read);
|
||||
|
||||
this.updateResourceIdentifierVisibility(value);
|
||||
}
|
||||
|
||||
this.store.dispatch(
|
||||
selectGlobalAccessPolicy({
|
||||
request: {
|
||||
resourceAction: {
|
||||
resource: this.policyForm.get('resource')?.value,
|
||||
action: this.policyForm.get('action')?.value,
|
||||
resourceIdentifier: this.policyForm.get('resourceIdentifier')?.value
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private globalPolicySupportsReadWrite(resource: string): boolean {
|
||||
return (
|
||||
resource === 'controller' ||
|
||||
resource === 'parameter-contexts' ||
|
||||
resource === 'counters' ||
|
||||
resource === 'policies' ||
|
||||
resource === 'tenants'
|
||||
);
|
||||
}
|
||||
|
||||
private globalPolicySupportsWrite(resource: string): boolean {
|
||||
return resource === 'proxy' || resource === 'restricted-components';
|
||||
}
|
||||
|
||||
private updateResourceIdentifierVisibility(resource: string): void {
|
||||
if (resource === 'restricted-components') {
|
||||
this.supportsResourceIdentifier = true;
|
||||
this.policyForm.addControl('resourceIdentifier', new FormControl(''));
|
||||
} else {
|
||||
this.supportsResourceIdentifier = false;
|
||||
this.policyForm.removeControl('resourceIdentifier');
|
||||
}
|
||||
}
|
||||
|
||||
actionChanged(): void {
|
||||
this.store.dispatch(
|
||||
selectGlobalAccessPolicy({
|
||||
request: {
|
||||
resourceAction: {
|
||||
resource: this.policyForm.get('resource')?.value,
|
||||
action: this.policyForm.get('action')?.value,
|
||||
resourceIdentifier: this.policyForm.get('resourceIdentifier')?.value
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
resourceIdentifierChanged(): void {
|
||||
this.store.dispatch(
|
||||
selectGlobalAccessPolicy({
|
||||
request: {
|
||||
resourceAction: {
|
||||
resource: this.policyForm.get('resource')?.value,
|
||||
action: this.policyForm.get('action')?.value,
|
||||
resourceIdentifier: this.policyForm.get('resourceIdentifier')?.value
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getTemplateForInheritedPolicy(policy: AccessPolicyEntity): TemplateRef<any> {
|
||||
if (policy.component.resource === '/policies') {
|
||||
return this.inheritedFromPolicies;
|
||||
} else if (policy.component.resource === '/controller') {
|
||||
return this.inheritedFromController;
|
||||
}
|
||||
|
||||
return this.inheritedFromNoRestrictions;
|
||||
}
|
||||
|
||||
createNewPolicy(): void {
|
||||
this.store.dispatch(createAccessPolicy());
|
||||
}
|
||||
|
||||
removeTenantFromPolicy(request: RemoveTenantFromPolicyRequest): void {
|
||||
this.store.dispatch(
|
||||
promptRemoveTenantFromPolicy({
|
||||
request
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
addTenantToPolicy(): void {
|
||||
this.store.dispatch(openAddTenantToPolicyDialog());
|
||||
}
|
||||
|
||||
deletePolicy(): void {
|
||||
this.store.dispatch(promptDeleteAccessPolicy());
|
||||
}
|
||||
|
||||
refreshGlobalAccessPolicy(): void {
|
||||
this.store.dispatch(reloadAccessPolicy());
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
// reload the current user to ensure the latest global policies
|
||||
this.store.dispatch(loadCurrentUser());
|
||||
|
||||
this.store.dispatch(resetAccessPolicyState());
|
||||
this.store.dispatch(resetTenantsState());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { GlobalAccessPolicies } from './global-access-policies.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 { GlobalAccessPoliciesRoutingModule } from './global-access-policies-routing.module';
|
||||
import { NifiTooltipDirective } from '../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||
import { PolicyTable } from '../common/policy-table/policy-table.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [GlobalAccessPolicies],
|
||||
exports: [GlobalAccessPolicies],
|
||||
imports: [
|
||||
CommonModule,
|
||||
GlobalAccessPoliciesRoutingModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
MatTableModule,
|
||||
MatSortModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
MatSelectModule,
|
||||
NifiTooltipDirective,
|
||||
PolicyTable
|
||||
]
|
||||
})
|
||||
export class GlobalAccessPoliciesModule {}
|
|
@ -31,6 +31,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('ConnectableBehavior', () => {
|
||||
let service: ConnectableBehavior;
|
||||
|
@ -55,6 +57,10 @@ describe('ConnectableBehavior', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('DraggableBehavior', () => {
|
||||
let service: DraggableBehavior;
|
||||
|
@ -60,6 +62,10 @@ describe('DraggableBehavior', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('EditableBehaviorService', () => {
|
||||
let service: EditableBehavior;
|
||||
|
@ -60,6 +62,10 @@ describe('EditableBehaviorService', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -31,6 +31,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('QuickSelectBehavior', () => {
|
||||
let service: QuickSelectBehavior;
|
||||
|
@ -55,6 +57,10 @@ describe('QuickSelectBehavior', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -30,6 +30,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('SelectableBehavior', () => {
|
||||
let service: SelectableBehavior;
|
||||
|
@ -54,6 +56,10 @@ describe('SelectableBehavior', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../state/current-user/current-user.sele
|
|||
import * as fromUser from '../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../state/parameter';
|
||||
import * as fromParameter from '../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('BirdseyeView', () => {
|
||||
let service: BirdseyeView;
|
||||
|
@ -60,6 +62,10 @@ describe('BirdseyeView', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
navigateToControllerServicesForProcessGroup,
|
||||
navigateToEditComponent,
|
||||
navigateToEditCurrentProcessGroup,
|
||||
navigateToManageComponentPolicies,
|
||||
navigateToProvenanceForComponent,
|
||||
navigateToViewStatusHistoryForComponent,
|
||||
reloadFlow,
|
||||
|
@ -740,13 +741,57 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
// TODO - canManagePolicies
|
||||
return false;
|
||||
return (
|
||||
this.canvasUtils.supportsManagedAuthorizer() && this.canvasUtils.canManagePolicies(selection)
|
||||
);
|
||||
},
|
||||
clazz: 'fa fa-key',
|
||||
text: 'Manage access policies',
|
||||
action: () => {
|
||||
// TODO - managePolicies
|
||||
action: (selection: any) => {
|
||||
if (selection.empty()) {
|
||||
this.store.dispatch(
|
||||
navigateToManageComponentPolicies({
|
||||
request: {
|
||||
resource: 'process-groups',
|
||||
id: this.canvasUtils.getProcessGroupId()
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const selectionData = selection.datum();
|
||||
const componentType: ComponentType = selectionData.type;
|
||||
|
||||
let resource: string = 'process-groups';
|
||||
switch (componentType) {
|
||||
case ComponentType.Processor:
|
||||
resource = 'processors';
|
||||
break;
|
||||
case ComponentType.InputPort:
|
||||
resource = 'input-ports';
|
||||
break;
|
||||
case ComponentType.OutputPort:
|
||||
resource = 'output-ports';
|
||||
break;
|
||||
case ComponentType.Funnel:
|
||||
resource = 'funnels';
|
||||
break;
|
||||
case ComponentType.Label:
|
||||
resource = 'labels';
|
||||
break;
|
||||
case ComponentType.RemoteProcessGroup:
|
||||
resource = 'remote-process-groups';
|
||||
break;
|
||||
}
|
||||
|
||||
this.store.dispatch(
|
||||
navigateToManageComponentPolicies({
|
||||
request: {
|
||||
resource,
|
||||
id: selectionData.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -31,6 +31,8 @@ import { selectCurrentUser } from '../../../state/current-user/current-user.sele
|
|||
import * as fromUser from '../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../state/parameter';
|
||||
import * as fromParameter from '../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('CanvasUtils', () => {
|
||||
let service: CanvasUtils;
|
||||
|
@ -55,6 +57,10 @@ describe('CanvasUtils', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -35,6 +35,9 @@ import { NiFiCommon } from '../../../service/nifi-common.service';
|
|||
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';
|
||||
import { FlowConfiguration } from '../../../state/flow-configuration';
|
||||
import { initialState as initialFlowConfigurationState } from '../../../state/flow-configuration/flow-configuration.reducer';
|
||||
import { selectFlowConfiguration } from '../../../state/flow-configuration/flow-configuration.selectors';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -49,6 +52,7 @@ export class CanvasUtils {
|
|||
private parentProcessGroupId: string | null = initialFlowState.flow.processGroupFlow.parentGroupId;
|
||||
private canvasPermissions: Permissions = initialFlowState.flow.permissions;
|
||||
private currentUser: CurrentUser = initialUserState.user;
|
||||
private flowConfiguration: FlowConfiguration | null = initialFlowConfigurationState.flowConfiguration;
|
||||
private connections: any[] = [];
|
||||
|
||||
private readonly humanizeDuration: Humanizer;
|
||||
|
@ -94,6 +98,13 @@ export class CanvasUtils {
|
|||
.subscribe((user) => {
|
||||
this.currentUser = user;
|
||||
});
|
||||
|
||||
this.store
|
||||
.select(selectFlowConfiguration)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((flowConfiguration) => {
|
||||
this.flowConfiguration = flowConfiguration;
|
||||
});
|
||||
}
|
||||
|
||||
public hasDownstream(selection: any): boolean {
|
||||
|
@ -453,6 +464,40 @@ export class CanvasUtils {
|
|||
return selection.size() === 1 && selection.classed('funnel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the user can configure or open the policy management page.
|
||||
*/
|
||||
public canManagePolicies(selection: any): boolean {
|
||||
// ensure 0 or 1 components selected
|
||||
if (selection.size() <= 1) {
|
||||
// if something is selected, ensure it's not a connection
|
||||
if (!selection.empty() && this.isConnection(selection)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ensure access to read tenants
|
||||
return this.canAccessTenants();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public supportsManagedAuthorizer(): boolean {
|
||||
if (this.flowConfiguration) {
|
||||
return this.flowConfiguration.supportsManagedAuthorizer;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the current user can access tenants.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public canAccessTenants(): boolean {
|
||||
return this.currentUser.tenantsPermissions.canRead === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the current user can access provenance for the specified component.
|
||||
*
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../state/current-user/current-user.sele
|
|||
import * as fromUser from '../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../state/parameter';
|
||||
import * as fromParameter from '../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('CanvasView', () => {
|
||||
let service: CanvasView;
|
||||
|
@ -60,6 +62,10 @@ describe('CanvasView', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('ConnectionManager', () => {
|
||||
let service: ConnectionManager;
|
||||
|
@ -60,6 +62,10 @@ describe('ConnectionManager', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('FunnelManager', () => {
|
||||
let service: FunnelManager;
|
||||
|
@ -60,6 +62,10 @@ describe('FunnelManager', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('LabelManager', () => {
|
||||
let service: LabelManager;
|
||||
|
@ -60,6 +62,10 @@ describe('LabelManager', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('PortManager', () => {
|
||||
let service: PortManager;
|
||||
|
@ -60,6 +62,10 @@ describe('PortManager', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('ProcessGroupManager', () => {
|
||||
let service: ProcessGroupManager;
|
||||
|
@ -60,6 +62,10 @@ describe('ProcessGroupManager', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('ProcessorManager', () => {
|
||||
let service: ProcessorManager;
|
||||
|
@ -60,6 +62,10 @@ describe('ProcessorManager', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -32,6 +32,8 @@ import { selectCurrentUser } from '../../../../state/current-user/current-user.s
|
|||
import * as fromUser from '../../../../state/current-user/current-user.reducer';
|
||||
import { parameterFeatureKey } from '../../state/parameter';
|
||||
import * as fromParameter from '../../state/parameter/parameter.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('RemoteProcessGroupManager', () => {
|
||||
let service: RemoteProcessGroupManager;
|
||||
|
@ -60,6 +62,10 @@ describe('RemoteProcessGroupManager', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -45,6 +45,7 @@ import {
|
|||
MoveComponentsRequest,
|
||||
NavigateToComponentRequest,
|
||||
NavigateToControllerServicesRequest,
|
||||
NavigateToManageComponentPoliciesRequest,
|
||||
OpenComponentDialogRequest,
|
||||
OpenGroupComponentsDialogRequest,
|
||||
LoadChildProcessGroupRequest,
|
||||
|
@ -295,6 +296,11 @@ export const navigateToEditComponent = createAction(
|
|||
props<{ request: OpenComponentDialogRequest }>()
|
||||
);
|
||||
|
||||
export const navigateToManageComponentPolicies = createAction(
|
||||
`${CANVAS_PREFIX} Navigate To Manage Component Policies`,
|
||||
props<{ request: NavigateToManageComponentPoliciesRequest }>()
|
||||
);
|
||||
|
||||
export const editComponent = createAction(
|
||||
`${CANVAS_PREFIX} Edit Component`,
|
||||
props<{ request: EditComponentDialogRequest }>()
|
||||
|
|
|
@ -674,6 +674,18 @@ export class FlowEffects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToManageComponentPolicies$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.navigateToManageComponentPolicies),
|
||||
map((action) => action.request),
|
||||
tap((request) => {
|
||||
this.router.navigate(['/access-policies', 'read', 'component', request.resource, request.id]);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToViewStatusHistoryForComponent$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
|
|
@ -215,6 +215,11 @@ export interface OpenComponentDialogRequest {
|
|||
type: ComponentType;
|
||||
}
|
||||
|
||||
export interface NavigateToManageComponentPoliciesRequest {
|
||||
resource: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface EditComponentDialogRequest {
|
||||
type: ComponentType;
|
||||
uri: string;
|
||||
|
|
|
@ -63,6 +63,7 @@ import { initialState } from '../../state/flow/flow.reducer';
|
|||
import { ContextMenuDefinitionProvider } from '../../../../ui/common/context-menu/context-menu.component';
|
||||
import { CanvasContextMenu } from '../../service/canvas-context-menu.service';
|
||||
import { getStatusHistoryAndOpenDialog } from '../../../../state/status-history/status-history.actions';
|
||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'fd-canvas',
|
||||
|
@ -270,6 +271,7 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
this.createSvg();
|
||||
this.canvasView.init(this.viewContainerRef, this.svg, this.canvas);
|
||||
|
||||
this.store.dispatch(loadFlowConfiguration());
|
||||
this.store.dispatch(startProcessGroupPolling());
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,11 @@ import {
|
|||
import { NavigationControl } from './navigation-control/navigation-control.component';
|
||||
import { OperationControl } from './operation-control/operation-control.component';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { NiFiState } from '../../../../../state';
|
||||
import {
|
||||
selectFlowConfiguration,
|
||||
selectSupportsManagedAuthorizer
|
||||
} from '../../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
|
||||
@Component({
|
||||
selector: 'graph-controls',
|
||||
|
@ -38,6 +43,7 @@ export class GraphControls {
|
|||
navigationCollapsed$ = this.store.select(selectNavigationCollapsed);
|
||||
operationCollapsed$ = this.store.select(selectOperationCollapsed);
|
||||
breadcrumbEntity$ = this.store.select(selectBreadcrumbs);
|
||||
supportsManagedAuthorizer$ = this.store.select(selectSupportsManagedAuthorizer);
|
||||
|
||||
constructor(private store: Store<CanvasState>) {}
|
||||
constructor(private store: Store<NiFiState>) {}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
<button
|
||||
class="nifi-button mr-2"
|
||||
type="button"
|
||||
*ngIf="supportsManagedAuthorizer()"
|
||||
[disabled]="!canManageAccess(selection)"
|
||||
(click)="manageAccess(selection)">
|
||||
<i class="fa fa-key"></i>
|
||||
|
|
|
@ -62,7 +62,6 @@ div.operation-control {
|
|||
|
||||
.operation-context-name {
|
||||
font-size: 15px;
|
||||
font-family: Roboto;
|
||||
color: #262626;
|
||||
height: 20px;
|
||||
width: 225px;
|
||||
|
@ -73,14 +72,12 @@ div.operation-control {
|
|||
|
||||
.operation-context-type {
|
||||
font-size: 12px;
|
||||
font-family: Roboto;
|
||||
color: #728e9b;
|
||||
}
|
||||
|
||||
.operation-context-id {
|
||||
height: 18px;
|
||||
font-size: 12px;
|
||||
font-family: Roboto;
|
||||
color: #775351;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
getParameterContextsAndOpenGroupComponentsDialog,
|
||||
navigateToEditComponent,
|
||||
navigateToEditCurrentProcessGroup,
|
||||
navigateToManageComponentPolicies,
|
||||
setOperationCollapsed,
|
||||
startComponents,
|
||||
startCurrentProcessGroup,
|
||||
|
@ -40,6 +41,7 @@ import {
|
|||
} from '../../../../state/flow';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { BreadcrumbEntity } from '../../../../state/shared';
|
||||
import { ComponentType } from '../../../../../../state/shared';
|
||||
|
||||
@Component({
|
||||
selector: 'operation-control',
|
||||
|
@ -199,13 +201,59 @@ export class OperationControl {
|
|||
}
|
||||
}
|
||||
|
||||
supportsManagedAuthorizer(): boolean {
|
||||
return this.canvasUtils.supportsManagedAuthorizer();
|
||||
}
|
||||
|
||||
canManageAccess(selection: any): boolean {
|
||||
// TODO
|
||||
return false;
|
||||
return this.canvasUtils.canManagePolicies(selection);
|
||||
}
|
||||
|
||||
manageAccess(selection: any): void {
|
||||
// TODO
|
||||
if (selection.empty()) {
|
||||
this.store.dispatch(
|
||||
navigateToManageComponentPolicies({
|
||||
request: {
|
||||
resource: 'process-groups',
|
||||
id: this.breadcrumbEntity.id
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const selectionData = selection.datum();
|
||||
const componentType: ComponentType = selectionData.type;
|
||||
|
||||
let resource: string = 'process-groups';
|
||||
switch (componentType) {
|
||||
case ComponentType.Processor:
|
||||
resource = 'processors';
|
||||
break;
|
||||
case ComponentType.InputPort:
|
||||
resource = 'input-ports';
|
||||
break;
|
||||
case ComponentType.OutputPort:
|
||||
resource = 'output-ports';
|
||||
break;
|
||||
case ComponentType.Funnel:
|
||||
resource = 'funnels';
|
||||
break;
|
||||
case ComponentType.Label:
|
||||
resource = 'labels';
|
||||
break;
|
||||
case ComponentType.RemoteProcessGroup:
|
||||
resource = 'remote-process-groups';
|
||||
break;
|
||||
}
|
||||
|
||||
this.store.dispatch(
|
||||
navigateToManageComponentPolicies({
|
||||
request: {
|
||||
resource,
|
||||
id: selectionData.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
canEnable(selection: any): boolean {
|
||||
|
|
|
@ -122,19 +122,31 @@
|
|||
<i class="fa fa-fw mr-2"></i>
|
||||
System Diagnostics
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button
|
||||
mat-menu-item
|
||||
class="global-menu-item"
|
||||
[routerLink]="['/users']"
|
||||
[disabled]="!user.tenantsPermissions.canRead">
|
||||
<i class="fa fa-fw fa-users mr-2"></i>
|
||||
Users
|
||||
</button>
|
||||
<button mat-menu-item class="global-menu-item">
|
||||
<i class="fa fa-fw fa-key mr-2"></i>
|
||||
Policies
|
||||
</button>
|
||||
<ng-container *ngIf="flowConfiguration$ | async as flowConfiguration">
|
||||
<ng-container *ngIf="flowConfiguration.supportsManagedAuthorizer">
|
||||
<mat-divider></mat-divider>
|
||||
<button
|
||||
mat-menu-item
|
||||
class="global-menu-item"
|
||||
[routerLink]="['/users']"
|
||||
[disabled]="!user.tenantsPermissions.canRead">
|
||||
<i class="fa fa-fw fa-users mr-2"></i>
|
||||
Users
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
class="global-menu-item"
|
||||
[routerLink]="['/access-policies', 'global']"
|
||||
[disabled]="
|
||||
!user.tenantsPermissions.canRead ||
|
||||
!user.policiesPermissions.canRead ||
|
||||
!user.policiesPermissions.canWrite
|
||||
">
|
||||
<i class="fa fa-fw fa-key mr-2"></i>
|
||||
Policies
|
||||
</button>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-menu-item class="global-menu-item">
|
||||
<i class="fa fa-fw fa-question-circle mr-2"></i>
|
||||
|
|
|
@ -38,6 +38,8 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
|||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { selectCurrentUser } from '../../../../../state/current-user/current-user.selectors';
|
||||
import * as fromUser from '../../../../../state/current-user/current-user.reducer';
|
||||
import { selectFlowConfiguration } from '../../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import * as fromFlowConfiguration from '../../../../../state/flow-configuration/flow-configuration.reducer';
|
||||
|
||||
describe('HeaderComponent', () => {
|
||||
let component: HeaderComponent;
|
||||
|
@ -105,6 +107,10 @@ describe('HeaderComponent', () => {
|
|||
{
|
||||
selector: selectCurrentUser,
|
||||
value: fromUser.initialState.user
|
||||
},
|
||||
{
|
||||
selector: selectFlowConfiguration,
|
||||
value: fromFlowConfiguration.initialState.flowConfiguration
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -40,6 +40,7 @@ import { RouterLink } from '@angular/router';
|
|||
import { FlowStatus } from './flow-status/flow-status.component';
|
||||
import { getNodeStatusHistoryAndOpenDialog } from '../../../../../state/status-history/status-history.actions';
|
||||
import { getSystemDiagnosticsAndOpenDialog } from '../../../../../state/system-diagnostics/system-diagnostics.actions';
|
||||
import { selectFlowConfiguration } from '../../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
|
||||
@Component({
|
||||
selector: 'fd-header',
|
||||
|
@ -66,6 +67,7 @@ export class HeaderComponent {
|
|||
clusterSummary$ = this.store.select(selectClusterSummary);
|
||||
controllerBulletins$ = this.store.select(selectControllerBulletins);
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration);
|
||||
currentProcessGroupId$ = this.store.select(selectCurrentProcessGroupId);
|
||||
|
||||
constructor(
|
||||
|
|
|
@ -43,6 +43,9 @@
|
|||
[controllerServices]="serviceState.controllerServices"
|
||||
[formatScope]="formatScope(serviceState.breadcrumb)"
|
||||
[definedByCurrentGroup]="definedByCurrentGroup(serviceState.breadcrumb)"
|
||||
[currentUser]="(currentUser$ | async)!"
|
||||
[flowConfiguration]="(flowConfiguration$ | async)!"
|
||||
[canModifyParent]="canModifyParent(serviceState.breadcrumb)"
|
||||
(selectControllerService)="selectControllerService($event)"
|
||||
(configureControllerService)="configureControllerService($event)"
|
||||
(enableControllerService)="enableControllerService($event)"
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { filter, switchMap, take, tap } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
@ -41,19 +41,25 @@ import {
|
|||
import { initialState } from '../../state/controller-services/controller-services.reducer';
|
||||
import { ControllerServiceEntity } from '../../../../state/shared';
|
||||
import { BreadcrumbEntity } from '../../state/shared';
|
||||
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'controller-services',
|
||||
templateUrl: './controller-services.component.html',
|
||||
styleUrls: ['./controller-services.component.scss']
|
||||
})
|
||||
export class ControllerServices implements OnDestroy {
|
||||
export class ControllerServices implements OnInit, OnDestroy {
|
||||
serviceState$ = this.store.select(selectControllerServicesState);
|
||||
selectedServiceId$ = this.store.select(selectControllerServiceIdFromRoute);
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration);
|
||||
|
||||
private currentProcessGroupId!: string;
|
||||
|
||||
constructor(private store: Store<ControllerServicesState>) {
|
||||
constructor(private store: Store<NiFiState>) {
|
||||
// load the controller services using the process group id from the route
|
||||
this.store
|
||||
.select(selectProcessGroupIdFromRoute)
|
||||
|
@ -98,6 +104,10 @@ export class ControllerServices implements OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(loadFlowConfiguration());
|
||||
}
|
||||
|
||||
isInitialLoading(state: ControllerServicesState): boolean {
|
||||
// using the current timestamp to detect the initial load event
|
||||
return state.loadedTimestamp == initialState.loadedTimestamp;
|
||||
|
@ -189,6 +199,28 @@ export class ControllerServices implements OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
canModifyParent(breadcrumb: BreadcrumbEntity): (entity: ControllerServiceEntity) => boolean {
|
||||
const breadcrumbs: BreadcrumbEntity[] = [];
|
||||
|
||||
let currentBreadcrumb: BreadcrumbEntity | undefined = breadcrumb;
|
||||
while (currentBreadcrumb != null) {
|
||||
breadcrumbs.push(currentBreadcrumb);
|
||||
currentBreadcrumb = currentBreadcrumb.parentBreadcrumb;
|
||||
}
|
||||
|
||||
return (entity: ControllerServiceEntity): boolean => {
|
||||
const entityBreadcrumb: BreadcrumbEntity | undefined = breadcrumbs.find(
|
||||
(bc) => bc.id === entity.parentGroupId
|
||||
);
|
||||
|
||||
if (entityBreadcrumb) {
|
||||
return entityBreadcrumb.permissions.canWrite;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
selectControllerService(entity: ControllerServiceEntity): void {
|
||||
// this service listing shows all services in the current group and any
|
||||
// ancestor group. in this context we don't want the user to navigate away
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
<parameter-context-table
|
||||
[parameterContexts]="parameterContextListingState.parameterContexts"
|
||||
[selectedParameterContextId]="selectedParameterContextId$ | async"
|
||||
[currentUser]="(currentUser$ | async)!"
|
||||
[flowConfiguration]="(flowConfiguration$ | async)!"
|
||||
(selectParameterContext)="selectParameterContext($event)"
|
||||
(editParameterContext)="editParameterContext($event)"
|
||||
(deleteParameterContext)="deleteParameterContext($event)"></parameter-context-table>
|
||||
|
|
|
@ -35,6 +35,9 @@ import {
|
|||
import { initialState } from '../../state/parameter-context-listing/parameter-context-listing.reducer';
|
||||
import { filter, switchMap, take } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'parameter-context-listing',
|
||||
|
@ -44,6 +47,8 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|||
export class ParameterContextListing implements OnInit {
|
||||
parameterContextListingState$ = this.store.select(selectParameterContextListingState);
|
||||
selectedParameterContextId$ = this.store.select(selectParameterContextIdFromRoute);
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration);
|
||||
|
||||
constructor(private store: Store<ParameterContextListingState>) {
|
||||
this.store
|
||||
|
@ -72,6 +77,7 @@ export class ParameterContextListing implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(loadFlowConfiguration());
|
||||
this.store.dispatch(loadParameterContexts());
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import { MatTableModule } from '@angular/material/table';
|
|||
import { NifiTooltipDirective } from '../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||
import { ParameterContextTable } from './parameter-context-table/parameter-context-table.component';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ParameterContextListing, ParameterContextTable],
|
||||
|
@ -34,7 +35,8 @@ import { MatDialogModule } from '@angular/material/dialog';
|
|||
MatSortModule,
|
||||
MatTableModule,
|
||||
MatDialogModule,
|
||||
NifiTooltipDirective
|
||||
NifiTooltipDirective,
|
||||
RouterLink
|
||||
]
|
||||
})
|
||||
export class ParameterContextListingModule {}
|
||||
|
|
|
@ -82,7 +82,8 @@
|
|||
<div
|
||||
class="pointer fa fa-key"
|
||||
*ngIf="canManageAccessPolicies()"
|
||||
(click)="managePoliciesClicked(item, $event)"
|
||||
(click)="$event.stopPropagation()"
|
||||
[routerLink]="getPolicyLink(item)"
|
||||
title="Access Policies"></div>
|
||||
<!-- TODO go to parameter provider -->
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,8 @@ import { MatTableDataSource } from '@angular/material/table';
|
|||
import { MatSort } from '@angular/material/sort';
|
||||
import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
||||
import { ParameterContextEntity } from '../../../state/parameter-context-listing';
|
||||
import { FlowConfiguration } from '../../../../../state/flow-configuration';
|
||||
import { CurrentUser } from '../../../../../state/current-user';
|
||||
|
||||
@Component({
|
||||
selector: 'parameter-context-table',
|
||||
|
@ -43,7 +45,10 @@ export class ParameterContextTable implements AfterViewInit {
|
|||
return '';
|
||||
};
|
||||
}
|
||||
|
||||
@Input() selectedParameterContextId!: string;
|
||||
@Input() flowConfiguration!: FlowConfiguration;
|
||||
@Input() currentUser!: CurrentUser;
|
||||
|
||||
@Output() selectParameterContext: EventEmitter<ParameterContextEntity> = new EventEmitter<ParameterContextEntity>();
|
||||
@Output() editParameterContext: EventEmitter<ParameterContextEntity> = new EventEmitter<ParameterContextEntity>();
|
||||
|
@ -86,8 +91,10 @@ export class ParameterContextTable implements AfterViewInit {
|
|||
}
|
||||
|
||||
canDelete(entity: ParameterContextEntity): boolean {
|
||||
// TODO canModifyParameterContexts
|
||||
return this.canRead(entity) && this.canWrite(entity);
|
||||
const canModifyParameterContexts: boolean =
|
||||
this.currentUser.parameterContextPermissions.canRead &&
|
||||
this.currentUser.parameterContextPermissions.canWrite;
|
||||
return canModifyParameterContexts && this.canRead(entity) && this.canWrite(entity);
|
||||
}
|
||||
|
||||
deleteClicked(entity: ParameterContextEntity, event: MouseEvent): void {
|
||||
|
@ -96,12 +103,11 @@ export class ParameterContextTable implements AfterViewInit {
|
|||
}
|
||||
|
||||
canManageAccessPolicies(): boolean {
|
||||
// TODO nfCanvasUtils.isManagedAuthorizer() && nfCommon.canAccessTenants()
|
||||
return false;
|
||||
return this.flowConfiguration.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead;
|
||||
}
|
||||
|
||||
managePoliciesClicked(entity: ParameterContextEntity, event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
getPolicyLink(entity: ParameterContextEntity): string[] {
|
||||
return ['/access-policies', 'read', 'component', 'parameter-contexts', entity.id];
|
||||
}
|
||||
|
||||
select(entity: ParameterContextEntity): void {
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
[controllerServices]="serviceState.controllerServices"
|
||||
[formatScope]="formatScope"
|
||||
[definedByCurrentGroup]="definedByCurrentGroup"
|
||||
[currentUser]="currentUser"
|
||||
[canModifyParent]="canModifyParent(currentUser)"
|
||||
[flowConfiguration]="(flowConfiguration$ | async)!"
|
||||
(selectControllerService)="selectControllerService($event)"
|
||||
(configureControllerService)="configureControllerService($event)"
|
||||
(enableControllerService)="enableControllerService($event)"
|
||||
|
|
|
@ -41,8 +41,9 @@ import { filter, switchMap, take } from 'rxjs';
|
|||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
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';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||
import { CurrentUser } from '../../../../state/current-user';
|
||||
|
||||
@Component({
|
||||
selector: 'management-controller-services',
|
||||
|
@ -53,6 +54,7 @@ export class ManagementControllerServices implements OnInit, OnDestroy {
|
|||
serviceState$ = this.store.select(selectManagementControllerServicesState);
|
||||
selectedServiceId$ = this.store.select(selectControllerServiceIdFromRoute);
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration);
|
||||
|
||||
constructor(private store: Store<NiFiState>) {
|
||||
this.store
|
||||
|
@ -82,6 +84,7 @@ export class ManagementControllerServices implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(loadFlowConfiguration());
|
||||
this.store.dispatch(loadManagementControllerServices());
|
||||
}
|
||||
|
||||
|
@ -146,6 +149,11 @@ export class ManagementControllerServices implements OnInit, OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
canModifyParent(currentUser: CurrentUser): (entity: ControllerServiceEntity) => boolean {
|
||||
return (entity: ControllerServiceEntity) =>
|
||||
currentUser.controllerPermissions.canRead && currentUser.controllerPermissions.canWrite;
|
||||
}
|
||||
|
||||
selectControllerService(entity: ControllerServiceEntity): void {
|
||||
this.store.dispatch(
|
||||
selectControllerService({
|
||||
|
|
|
@ -121,8 +121,13 @@
|
|||
(click)="deleteClicked(item)"
|
||||
title="Delete"></div>
|
||||
<div class="pointer fa fa-tasks" *ngIf="canViewState(item)" title="View State"></div>
|
||||
<div
|
||||
class="pointer fa fa-key"
|
||||
*ngIf="canManageAccessPolicies()"
|
||||
(click)="$event.stopPropagation()"
|
||||
[routerLink]="getPolicyLink(item)"
|
||||
title="Access Policies"></div>
|
||||
</div>
|
||||
<div class="pointer fa fa-key" *ngIf="canManageAccessPolicies()" title="Access Policies"></div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ import { BulletinsTip } from '../../../../../ui/common/tooltips/bulletins-tip/bu
|
|||
import { ValidationErrorsTip } from '../../../../../ui/common/tooltips/validation-errors-tip/validation-errors-tip.component';
|
||||
import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
||||
import { BulletinsTipInput, TextTipInput, ValidationErrorsTipInput } from '../../../../../state/shared';
|
||||
import { FlowConfiguration } from '../../../../../state/flow-configuration';
|
||||
import { CurrentUser } from '../../../../../state/current-user';
|
||||
|
||||
@Component({
|
||||
selector: 'reporting-task-table',
|
||||
|
@ -48,6 +50,8 @@ export class ReportingTaskTable implements AfterViewInit {
|
|||
};
|
||||
}
|
||||
@Input() selectedReportingTaskId!: string;
|
||||
@Input() flowConfiguration!: FlowConfiguration;
|
||||
@Input() currentUser!: CurrentUser;
|
||||
|
||||
@Output() selectReportingTask: EventEmitter<ReportingTaskEntity> = new EventEmitter<ReportingTaskEntity>();
|
||||
@Output() deleteReportingTask: EventEmitter<ReportingTaskEntity> = new EventEmitter<ReportingTaskEntity>();
|
||||
|
@ -214,7 +218,8 @@ export class ReportingTaskTable implements AfterViewInit {
|
|||
}
|
||||
|
||||
canDelete(entity: ReportingTaskEntity): boolean {
|
||||
const canWriteParent: boolean = true; // TODO canModifyController()
|
||||
const canWriteParent: boolean =
|
||||
this.currentUser.controllerPermissions.canRead && this.currentUser.controllerPermissions.canWrite;
|
||||
return (
|
||||
(this.isDisabled(entity) || this.isStopped(entity)) &&
|
||||
this.canRead(entity) &&
|
||||
|
@ -237,8 +242,11 @@ export class ReportingTaskTable implements AfterViewInit {
|
|||
}
|
||||
|
||||
canManageAccessPolicies(): boolean {
|
||||
// TODO
|
||||
return false;
|
||||
return this.flowConfiguration.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead;
|
||||
}
|
||||
|
||||
getPolicyLink(entity: ReportingTaskEntity): string[] {
|
||||
return ['/access-policies', 'read', 'component', 'reporting-tasks', entity.id];
|
||||
}
|
||||
|
||||
select(entity: ReportingTaskEntity): void {
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
<reporting-task-table
|
||||
[selectedReportingTaskId]="selectedReportingTaskId$ | async"
|
||||
[reportingTasks]="reportingTaskState.reportingTasks"
|
||||
[currentUser]="currentUser"
|
||||
[flowConfiguration]="(flowConfiguration$ | async)!"
|
||||
(configureReportingTask)="configureReportingTask($event)"
|
||||
(selectReportingTask)="selectReportingTask($event)"
|
||||
(deleteReportingTask)="deleteReportingTask($event)"
|
||||
|
|
|
@ -40,6 +40,8 @@ import {
|
|||
import { initialState } from '../../state/reporting-tasks/reporting-tasks.reducer';
|
||||
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
|
||||
@Component({
|
||||
selector: 'reporting-tasks',
|
||||
|
@ -50,6 +52,7 @@ export class ReportingTasks implements OnInit, OnDestroy {
|
|||
reportingTaskState$ = this.store.select(selectReportingTasksState);
|
||||
selectedReportingTaskId$ = this.store.select(selectReportingTaskIdFromRoute);
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration);
|
||||
|
||||
constructor(private store: Store<NiFiState>) {
|
||||
this.store
|
||||
|
@ -79,6 +82,7 @@ export class ReportingTasks implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(loadFlowConfiguration());
|
||||
this.store.dispatch(loadReportingTasks());
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import { MatTableModule } from '@angular/material/table';
|
|||
import { NifiTooltipDirective } from '../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||
import { ReportingTaskTable } from './reporting-task-table/reporting-task-table.component';
|
||||
import { ControllerServiceTable } from '../../../../ui/common/controller-service/controller-service-table/controller-service-table.component';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ReportingTasks, ReportingTaskTable],
|
||||
|
@ -34,7 +35,8 @@ import { ControllerServiceTable } from '../../../../ui/common/controller-service
|
|||
MatSortModule,
|
||||
MatTableModule,
|
||||
NifiTooltipDirective,
|
||||
ControllerServiceTable
|
||||
ControllerServiceTable,
|
||||
RouterLink
|
||||
]
|
||||
})
|
||||
export class ReportingTasksModule {}
|
||||
|
|
|
@ -23,17 +23,19 @@
|
|||
<ng-template #loaded>
|
||||
<div class="flex flex-col h-full gap-y-2">
|
||||
<div class="flex-1" *ngIf="currentUser$ | async as user">
|
||||
<user-table
|
||||
[tenants]="{ users: userListingState.users, userGroups: userListingState.userGroups }"
|
||||
[selectedTenantId]="selectedTenantId$ | async"
|
||||
[currentUser]="(currentUser$ | async)!"
|
||||
[configurableUsersAndGroups]="true"
|
||||
(createTenant)="createTenant()"
|
||||
(selectTenant)="selectTenant($event)"
|
||||
(editTenant)="editTenant($event)"
|
||||
(deleteUser)="deleteUser($event)"
|
||||
(deleteUserGroup)="deleteUserGroup($event)"
|
||||
(viewAccessPolicies)="viewAccessPolicies($event)"></user-table>
|
||||
<ng-container *ngIf="(flowConfiguration$ | async)! as flowConfiguration">
|
||||
<user-table
|
||||
[tenants]="{ users: userListingState.users, userGroups: userListingState.userGroups }"
|
||||
[selectedTenantId]="selectedTenantId$ | async"
|
||||
[currentUser]="user"
|
||||
[configurableUsersAndGroups]="flowConfiguration.supportsConfigurableUsersAndGroups"
|
||||
(createTenant)="createTenant()"
|
||||
(selectTenant)="selectTenant($event)"
|
||||
(editTenant)="editTenant($event)"
|
||||
(deleteUser)="deleteUser($event)"
|
||||
(deleteUserGroup)="deleteUserGroup($event)"
|
||||
(viewAccessPolicies)="viewAccessPolicies($event)"></user-table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<div class="refresh-container flex items-center gap-x-2">
|
||||
|
|
|
@ -42,6 +42,8 @@ import {
|
|||
import { filter, switchMap, take } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { UserEntity, UserGroupEntity } from '../../../../state/shared';
|
||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
|
||||
@Component({
|
||||
selector: 'user-listing',
|
||||
|
@ -49,6 +51,7 @@ import { UserEntity, UserGroupEntity } from '../../../../state/shared';
|
|||
styleUrls: ['./user-listing.component.scss']
|
||||
})
|
||||
export class UserListing implements OnInit {
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration);
|
||||
userListingState$ = this.store.select(selectUserListingState);
|
||||
selectedTenantId$ = this.store.select(selectTenantIdFromRoute);
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
|
@ -124,6 +127,7 @@ export class UserListing implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(loadFlowConfiguration());
|
||||
this.store.dispatch(loadTenants());
|
||||
}
|
||||
|
||||
|
|
|
@ -19,24 +19,11 @@ 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],
|
||||
declarations: [UserListing],
|
||||
exports: [UserListing],
|
||||
imports: [
|
||||
CommonModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
MatTableModule,
|
||||
MatSortModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
MatSelectModule
|
||||
]
|
||||
imports: [CommonModule, NgxSkeletonLoaderModule, UserTable]
|
||||
})
|
||||
export class UserListingModule {}
|
||||
|
|
|
@ -172,8 +172,8 @@ describe('UserTable', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [UserTable],
|
||||
imports: [
|
||||
UserTable,
|
||||
MatTableModule,
|
||||
MatSortModule,
|
||||
MatInputModule,
|
||||
|
|
|
@ -16,13 +16,17 @@
|
|||
*/
|
||||
|
||||
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 { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule } 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';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
|
||||
export interface TenantItem {
|
||||
id: string;
|
||||
|
@ -39,7 +43,17 @@ export interface Tenants {
|
|||
|
||||
@Component({
|
||||
selector: 'user-table',
|
||||
standalone: true,
|
||||
templateUrl: './user-table.component.html',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
NgIf,
|
||||
MatTableModule,
|
||||
MatSortModule,
|
||||
MatInputModule
|
||||
],
|
||||
styleUrls: ['./user-table.component.scss', '../../../../../../assets/styles/listing-table.scss']
|
||||
})
|
||||
export class UserTable implements AfterViewInit {
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FlowConfigurationService {
|
||||
private static readonly API: string = '../nifi-api';
|
||||
|
||||
constructor(private httpClient: HttpClient) {}
|
||||
|
||||
getFlowConfiguration(): Observable<any> {
|
||||
return this.httpClient.get(`${FlowConfigurationService.API}/flow/config`);
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ export const authorizationGuard = (authorizationCheck: (user: CurrentUser) => bo
|
|||
return true;
|
||||
}
|
||||
|
||||
// TODO - replace with 404 error page
|
||||
// TODO - replace with 403 error page
|
||||
return router.parseUrl('/');
|
||||
})
|
||||
);
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
||||
import { CanMatchFn, Route, Router, UrlSegment } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { map } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { FlowConfiguration, FlowConfigurationState } from '../../state/flow-configuration';
|
||||
import { selectFlowConfiguration } from '../../state/flow-configuration/flow-configuration.selectors';
|
||||
|
||||
export const checkFlowConfiguration = (
|
||||
flowConfigurationCheck: (flowConfiguration: FlowConfiguration) => boolean
|
||||
): CanMatchFn => {
|
||||
return (route: Route, state: UrlSegment[]) => {
|
||||
const router: Router = inject(Router);
|
||||
const store: Store<FlowConfigurationState> = inject(Store<FlowConfigurationState>);
|
||||
|
||||
return store.select(selectFlowConfiguration).pipe(
|
||||
map((flowConfiguration) => {
|
||||
if (flowConfiguration && flowConfigurationCheck(flowConfiguration)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO - replace with 409 error page
|
||||
return router.parseUrl('/');
|
||||
})
|
||||
);
|
||||
};
|
||||
};
|
|
@ -504,4 +504,11 @@ export class NiFiCommon {
|
|||
public getPolicyTypeListing(value: string): SelectOption | undefined {
|
||||
return this.policyTypeListing.find((policy: SelectOption) => value === policy.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all policy types for every global resource.
|
||||
*/
|
||||
public getAllPolicyTypeListing(): SelectOption[] {
|
||||
return this.policyTypeListing;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ export class AboutEffects {
|
|||
this.aboutService.getAbout().pipe(
|
||||
map((response) =>
|
||||
AboutActions.loadAboutSuccess({
|
||||
response: response
|
||||
response
|
||||
})
|
||||
),
|
||||
catchError((error) => of(AboutActions.aboutApiError({ error: error.error })))
|
||||
|
|
|
@ -16,7 +16,11 @@
|
|||
*/
|
||||
|
||||
import { createAction, props } from '@ngrx/store';
|
||||
import { LoadExtensionTypesForCanvasResponse, LoadExtensionTypesForSettingsResponse } from './index';
|
||||
import {
|
||||
LoadExtensionTypesForCanvasResponse,
|
||||
LoadExtensionTypesForPoliciesResponse,
|
||||
LoadExtensionTypesForSettingsResponse
|
||||
} from './index';
|
||||
|
||||
export const loadExtensionTypesForCanvas = createAction('[Extension Types] Load Extension Types For Canvas');
|
||||
|
||||
|
@ -32,6 +36,13 @@ export const loadExtensionTypesForSettingsSuccess = createAction(
|
|||
props<{ response: LoadExtensionTypesForSettingsResponse }>()
|
||||
);
|
||||
|
||||
export const loadExtensionTypesForPolicies = createAction('[Extension Types] Load Extension Types For Policies');
|
||||
|
||||
export const loadExtensionTypesForPoliciesSuccess = createAction(
|
||||
'[Extension Types] Load Extension Types For Canvas Success',
|
||||
props<{ response: LoadExtensionTypesForPoliciesResponse }>()
|
||||
);
|
||||
|
||||
export const extensionTypesApiError = createAction(
|
||||
'[Extension Types] Extension Types Api Error',
|
||||
props<{ error: string }>()
|
||||
|
|
|
@ -86,4 +86,39 @@ export class ExtensionTypesEffects {
|
|||
)
|
||||
)
|
||||
);
|
||||
|
||||
loadExtensionTypesForPolicies$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ExtensionTypesActions.loadExtensionTypesForPolicies),
|
||||
switchMap(() =>
|
||||
combineLatest([
|
||||
this.extensionTypesService.getProcessorTypes(),
|
||||
this.extensionTypesService.getControllerServiceTypes(),
|
||||
this.extensionTypesService.getReportingTaskTypes(),
|
||||
this.extensionTypesService.getParameterProviderTypes(),
|
||||
this.extensionTypesService.getFlowAnalysisRuleTypes()
|
||||
]).pipe(
|
||||
map(
|
||||
([
|
||||
processorTypes,
|
||||
controllerServiceTypes,
|
||||
reportingTaskTypes,
|
||||
parameterProviderTypes,
|
||||
flowAnalysisRuleTypes
|
||||
]) =>
|
||||
ExtensionTypesActions.loadExtensionTypesForPoliciesSuccess({
|
||||
response: {
|
||||
processorTypes: processorTypes.processorTypes,
|
||||
controllerServiceTypes: controllerServiceTypes.controllerServiceTypes,
|
||||
reportingTaskTypes: reportingTaskTypes.reportingTaskTypes,
|
||||
parameterProviderTypes: parameterProviderTypes.parameterProviderTypes,
|
||||
flowAnalysisRuleTypes: flowAnalysisRuleTypes.flowAnalysisRuleTypes
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) => of(ExtensionTypesActions.extensionTypesApiError({ error: error.error })))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue