]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.ts
add stop-gap to fix compat with CPUs not supporting SSE 4.1
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / core / auth / role-form / role-form.component.ts
1 import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
2 import { FormControl, Validators } from '@angular/forms';
3 import { ActivatedRoute, Router } from '@angular/router';
4
5 import _ from 'lodash';
6 import { forkJoin as observableForkJoin } from 'rxjs';
7
8 import { RoleService } from '~/app/shared/api/role.service';
9 import { ScopeService } from '~/app/shared/api/scope.service';
10 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
11 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
12 import { CdForm } from '~/app/shared/forms/cd-form';
13 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
14 import { CdValidators } from '~/app/shared/forms/cd-validators';
15 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
16 import { NotificationService } from '~/app/shared/services/notification.service';
17 import { RoleFormMode } from './role-form-mode.enum';
18 import { RoleFormModel } from './role-form.model';
19
20 @Component({
21 selector: 'cd-role-form',
22 templateUrl: './role-form.component.html',
23 styleUrls: ['./role-form.component.scss']
24 })
25 export class RoleFormComponent extends CdForm implements OnInit {
26 @ViewChild('headerPermissionCheckboxTpl', { static: true })
27 headerPermissionCheckboxTpl: TemplateRef<any>;
28 @ViewChild('cellScopeCheckboxTpl', { static: true })
29 cellScopeCheckboxTpl: TemplateRef<any>;
30 @ViewChild('cellPermissionCheckboxTpl', { static: true })
31 cellPermissionCheckboxTpl: TemplateRef<any>;
32
33 roleForm: CdFormGroup;
34 response: RoleFormModel;
35
36 columns: CdTableColumn[];
37 scopes: Array<string> = [];
38 scopes_permissions: Array<any> = [];
39
40 roleFormMode = RoleFormMode;
41 mode: RoleFormMode;
42
43 action: string;
44 resource: string;
45
46 constructor(
47 private route: ActivatedRoute,
48 private router: Router,
49 private roleService: RoleService,
50 private scopeService: ScopeService,
51 private notificationService: NotificationService,
52 public actionLabels: ActionLabelsI18n
53 ) {
54 super();
55 this.resource = $localize`role`;
56 this.createForm();
57 this.listenToChanges();
58 }
59
60 createForm() {
61 this.roleForm = new CdFormGroup({
62 name: new FormControl('', {
63 validators: [Validators.required],
64 asyncValidators: [CdValidators.unique(this.roleService.exists, this.roleService)]
65 }),
66 description: new FormControl(''),
67 scopes_permissions: new FormControl({})
68 });
69 }
70
71 ngOnInit() {
72 this.columns = [
73 {
74 prop: 'scope',
75 name: $localize`All`,
76 flexGrow: 2,
77 cellTemplate: this.cellScopeCheckboxTpl,
78 headerTemplate: this.headerPermissionCheckboxTpl
79 },
80 {
81 prop: 'read',
82 name: $localize`Read`,
83 flexGrow: 1,
84 cellClass: 'text-center',
85 cellTemplate: this.cellPermissionCheckboxTpl,
86 headerTemplate: this.headerPermissionCheckboxTpl
87 },
88 {
89 prop: 'create',
90 name: $localize`Create`,
91 flexGrow: 1,
92 cellClass: 'text-center',
93 cellTemplate: this.cellPermissionCheckboxTpl,
94 headerTemplate: this.headerPermissionCheckboxTpl
95 },
96 {
97 prop: 'update',
98 name: $localize`Update`,
99 flexGrow: 1,
100 cellClass: 'text-center',
101 cellTemplate: this.cellPermissionCheckboxTpl,
102 headerTemplate: this.headerPermissionCheckboxTpl
103 },
104 {
105 prop: 'delete',
106 name: $localize`Delete`,
107 flexGrow: 1,
108 cellClass: 'text-center',
109 cellTemplate: this.cellPermissionCheckboxTpl,
110 headerTemplate: this.headerPermissionCheckboxTpl
111 }
112 ];
113 if (this.router.url.startsWith('/user-management/roles/edit')) {
114 this.mode = this.roleFormMode.editing;
115 this.action = this.actionLabels.EDIT;
116 } else {
117 this.action = this.actionLabels.CREATE;
118 }
119 if (this.mode === this.roleFormMode.editing) {
120 this.initEdit();
121 } else {
122 this.initCreate();
123 }
124 }
125
126 initCreate() {
127 // Load the scopes and initialize the default scopes/permissions data.
128 this.scopeService.list().subscribe((scopes: Array<string>) => {
129 this.scopes = scopes;
130 this.roleForm.get('scopes_permissions').setValue({});
131
132 this.loadingReady();
133 });
134 }
135
136 initEdit() {
137 // Disable the 'Name' input field.
138 this.roleForm.get('name').disable();
139 // Load the scopes and the role data.
140 this.route.params.subscribe((params: { name: string }) => {
141 const observables = [];
142 observables.push(this.scopeService.list());
143 observables.push(this.roleService.get(params.name));
144 observableForkJoin(observables).subscribe((resp: any[]) => {
145 this.scopes = resp[0];
146 ['name', 'description', 'scopes_permissions'].forEach((key) =>
147 this.roleForm.get(key).setValue(resp[1][key])
148 );
149
150 this.loadingReady();
151 });
152 });
153 }
154
155 listenToChanges() {
156 // Create/Update the data which is used by the data table to display the
157 // scopes/permissions every time the form field value has been changed.
158 this.roleForm.get('scopes_permissions').valueChanges.subscribe((value) => {
159 const scopes_permissions: any[] = [];
160 _.each(this.scopes, (scope) => {
161 // Set the defaults values.
162 const scope_permission: any = { read: false, create: false, update: false, delete: false };
163 scope_permission['scope'] = scope;
164 // Apply settings from the given value if they exist.
165 if (scope in value) {
166 _.each(value[scope], (permission) => {
167 scope_permission[permission] = true;
168 });
169 }
170 scopes_permissions.push(scope_permission);
171 });
172 this.scopes_permissions = scopes_permissions;
173 });
174 }
175
176 /**
177 * Checks if the specified row checkbox needs to be rendered as checked.
178 * @param {string} scope The scope to be checked, e.g. 'cephfs', 'grafana',
179 * 'osd', 'pool' ...
180 * @return Returns true if all permissions (read, create, update, delete)
181 * are checked for the specified scope, otherwise false.
182 */
183 isRowChecked(scope: string) {
184 const scope_permission = _.find(this.scopes_permissions, (o) => {
185 return o['scope'] === scope;
186 });
187 if (_.isUndefined(scope_permission)) {
188 return false;
189 }
190 return (
191 scope_permission['read'] &&
192 scope_permission['create'] &&
193 scope_permission['update'] &&
194 scope_permission['delete']
195 );
196 }
197
198 /**
199 * Checks if the specified header checkbox needs to be rendered as checked.
200 * @param {string} property The property/permission (read, create,
201 * update, delete) to be checked. If 'scope' is given, all permissions
202 * are checked.
203 * @return Returns true if specified property/permission is selected
204 * for all scopes, otherwise false.
205 */
206 isHeaderChecked(property: string) {
207 let permissions = [property];
208 if ('scope' === property) {
209 permissions = ['read', 'create', 'update', 'delete'];
210 }
211 return permissions.every((permission) => {
212 return this.scopes_permissions.every((scope_permission) => {
213 return scope_permission[permission];
214 });
215 });
216 }
217
218 onClickCellCheckbox(scope: string, property: string, event: any = null) {
219 // Use a copy of the form field data to do not trigger the redrawing of the
220 // data table with every change.
221 const scopes_permissions = _.cloneDeep(this.roleForm.getValue('scopes_permissions'));
222 let permissions = [property];
223 if ('scope' === property) {
224 permissions = ['read', 'create', 'update', 'delete'];
225 }
226 if (!(scope in scopes_permissions)) {
227 scopes_permissions[scope] = [];
228 }
229 // Add or remove the given permission(s) depending on the click event or if no
230 // click event is given then add/remove them if they are absent/exist.
231 if (
232 (event && event.target['checked']) ||
233 !_.isEqual(permissions.sort(), _.intersection(scopes_permissions[scope], permissions).sort())
234 ) {
235 scopes_permissions[scope] = _.union(scopes_permissions[scope], permissions);
236 } else {
237 scopes_permissions[scope] = _.difference(scopes_permissions[scope], permissions);
238 if (_.isEmpty(scopes_permissions[scope])) {
239 _.unset(scopes_permissions, scope);
240 }
241 }
242 this.roleForm.get('scopes_permissions').setValue(scopes_permissions);
243 }
244
245 onClickHeaderCheckbox(property: 'scope' | 'read' | 'create' | 'update' | 'delete', event: any) {
246 // Use a copy of the form field data to do not trigger the redrawing of the
247 // data table with every change.
248 const scopes_permissions = _.cloneDeep(this.roleForm.getValue('scopes_permissions'));
249 let permissions = [property];
250 if ('scope' === property) {
251 permissions = ['read', 'create', 'update', 'delete'];
252 }
253 _.each(permissions, (permission) => {
254 _.each(this.scopes, (scope) => {
255 if (event.target['checked']) {
256 scopes_permissions[scope] = _.union(scopes_permissions[scope], [permission]);
257 } else {
258 scopes_permissions[scope] = _.difference(scopes_permissions[scope], [permission]);
259 if (_.isEmpty(scopes_permissions[scope])) {
260 _.unset(scopes_permissions, scope);
261 }
262 }
263 });
264 });
265 this.roleForm.get('scopes_permissions').setValue(scopes_permissions);
266 }
267
268 getRequest(): RoleFormModel {
269 const roleFormModel = new RoleFormModel();
270 ['name', 'description', 'scopes_permissions'].forEach(
271 (key) => (roleFormModel[key] = this.roleForm.get(key).value)
272 );
273 return roleFormModel;
274 }
275
276 createAction() {
277 const roleFormModel = this.getRequest();
278 this.roleService.create(roleFormModel).subscribe(
279 () => {
280 this.notificationService.show(
281 NotificationType.success,
282 $localize`Created role '${roleFormModel.name}'`
283 );
284 this.router.navigate(['/user-management/roles']);
285 },
286 () => {
287 this.roleForm.setErrors({ cdSubmitButton: true });
288 }
289 );
290 }
291
292 editAction() {
293 const roleFormModel = this.getRequest();
294 this.roleService.update(roleFormModel).subscribe(
295 () => {
296 this.notificationService.show(
297 NotificationType.success,
298 $localize`Updated role '${roleFormModel.name}'`
299 );
300 this.router.navigate(['/user-management/roles']);
301 },
302 () => {
303 this.roleForm.setErrors({ cdSubmitButton: true });
304 }
305 );
306 }
307
308 submit() {
309 if (this.mode === this.roleFormMode.editing) {
310 this.editAction();
311 } else {
312 this.createAction();
313 }
314 }
315 }