1 import { Component, OnInit, Output, EventEmitter, Input, inject } from '@angular/core';
2 import { BehaviorSubject, Observable, of } from 'rxjs';
3 import { catchError, map, switchMap, defaultIfEmpty } from 'rxjs/operators';
5 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
6 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
7 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
8 import { CephfsService } from '~/app/shared/api/cephfs.service';
9 import { ClusterService } from '~/app/shared/api/cluster.service';
11 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
12 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
13 import { Validators, AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
14 import { CdForm } from '~/app/shared/forms/cd-form';
15 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
16 import { FinishedTask } from '~/app/shared/models/finished-task';
17 import { FilesystemRow, MirroringEntityRow } from '~/app/shared/models/cephfs.model';
18 import { CephAuthUser } from '~/app/shared/models/cluster.model';
21 selector: 'cd-cephfs-mirroring-entity',
22 templateUrl: './cephfs-mirroring-entity.component.html',
23 styleUrls: ['./cephfs-mirroring-entity.component.scss'],
26 export class CephfsMirroringEntityComponent extends CdForm implements OnInit {
27 columns: CdTableColumn[];
28 selection = new CdTableSelection();
30 subject$ = new BehaviorSubject<void>(undefined);
31 entities$: Observable<MirroringEntityRow[]>;
32 context: CdTableFetchDataContext;
34 { name: $localize`MDS`, permission: 'rwps' },
35 { name: $localize`MON`, permission: 'rwps' },
36 { name: $localize`OSD`, permission: 'rwps' }
39 isCreatingNewEntity = true;
40 showCreateRequirementsWarning = true;
41 showCreateCapabilitiesInfo = true;
42 showSelectRequirementsWarning = true;
43 showSelectEntityInfo = true;
45 entityForm: CdFormGroup;
47 readonly userEntityHelperText = $localize`Ceph Authentication entity used by mirroring.`;
49 @Input() selectedFilesystem: FilesystemRow | null = null;
50 @Output() entitySelected = new EventEmitter<string | null>();
51 isSubmitting: boolean = false;
53 private cephfsService = inject(CephfsService);
54 private clusterService = inject(ClusterService);
55 private taskWrapperService = inject(TaskWrapperService);
56 private formBuilder = inject(CdFormBuilder);
59 const noClientPrefix: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
60 const value = (control.value ?? '').toString().trim();
61 if (!value) return null;
62 return value.startsWith('client.') ? { forbiddenClientPrefix: true } : null;
65 this.entityForm = this.formBuilder.group({
66 user_entity: ['', [Validators.required, noClientPrefix]]
71 name: $localize`Entity ID`,
76 name: $localize`MDS capabilities`,
81 name: $localize`MON capabilities`,
86 name: $localize`OSD capabilities`,
92 this.entities$ = this.subject$.pipe(
94 this.clusterService.listUser().pipe(
95 switchMap((users) => {
96 const typedUsers = (users as CephAuthUser[]) || [];
97 const filteredEntities = typedUsers.filter((entity) => {
98 if (entity.entity?.startsWith('client.')) {
99 const caps = entity.caps || {};
100 const mdsCaps = caps.mds || '-';
102 const fsName = this.selectedFilesystem?.name || '';
103 const isValid = mdsCaps.includes(`fsname=${fsName}`);
110 const rows: MirroringEntityRow[] = filteredEntities.map((entity) => {
111 const caps = entity.caps || {};
112 const mdsCaps = caps.mds || '-';
113 const monCaps = caps.mon || '-';
114 const osdCaps = caps.osd || '-';
117 entity: entity.entity,
127 this.context?.error();
137 submitAction(): void {
138 if (!this.entityForm.valid) {
139 this.entityForm.markAllAsTouched();
143 const clientEntity = (this.entityForm.get('user_entity')?.value || '').toString().trim();
144 const fullEntity = `client.${clientEntity}`;
145 const fsName = this.selectedFilesystem?.name;
148 user_entity: fullEntity,
150 { entity: 'mds', cap: 'allow *' },
151 { entity: 'mgr', cap: 'allow *' },
152 { entity: 'mon', cap: 'allow *' },
153 { entity: 'osd', cap: 'allow *' }
157 this.isSubmitting = true;
159 this.taskWrapperService
160 .wrapTaskAroundCall({
161 task: new FinishedTask(`ceph-user/create`, {
162 userEntity: fullEntity,
165 call: this.clusterService.createUser(payload).pipe(
167 return { ...(res as Record<string, unknown>), __taskCompleted: true };
172 defaultIfEmpty(null),
175 return this.cephfsService.setAuth(fsName, clientEntity, ['/', 'rwps'], false);
182 this.isSubmitting = false;
183 this.entityForm.reset();
184 this.handleEntityCreated(fullEntity);
189 private handleEntityCreated(entityId: string) {
190 this.loadEntities(this.context);
191 this.entitySelected.emit(entityId);
192 this.isCreatingNewEntity = false;
195 loadEntities(context?: CdTableFetchDataContext) {
196 this.context = context;
197 this.subject$.next();
200 updateSelection(selection: CdTableSelection) {
201 this.selection = selection;
202 const selectedRow = selection?.first();
203 this.entitySelected.emit(selectedRow ? selectedRow.entity : null);
206 onCreateEntitySelected() {
207 this.isCreatingNewEntity = true;
208 this.showCreateRequirementsWarning = true;
209 this.showCreateCapabilitiesInfo = true;
212 onExistingEntitySelected() {
213 this.isCreatingNewEntity = false;
214 this.showSelectRequirementsWarning = true;
215 this.showSelectEntityInfo = true;
218 onDismissCreateRequirementsWarning() {
219 this.showCreateRequirementsWarning = false;
222 onDismissCreateCapabilitiesInfo() {
223 this.showCreateCapabilitiesInfo = false;
226 onDismissSelectRequirementsWarning() {
227 this.showSelectRequirementsWarning = false;
230 onDismissSelectEntityInfo() {
231 this.showSelectEntityInfo = false;