Skip to content

Commit 87497b6

Browse files
authored
Merge pull request #285 from MultiDirectoryLab/feature/MD-1082
[MD-1082]: Feature: Rename LDAP Entities
2 parents 1dee6c3 + 4d7356d commit 87497b6

17 files changed

Lines changed: 1311 additions & 211 deletions

File tree

projects/multidirectory-app/src/app/components/modals/components/core/context-menu/context-menu.component.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44
{{ 'catalog-content.more' | transloco }}
55
</div>
66
}
7+
@if (isSelectedRowsOfType(LdapEntryType.User)) {
8+
<div class="context-item" (click)="openRenameUserDialog()">
9+
{{ 'catalog-content.rename' | transloco }}
10+
</div>
11+
}
12+
13+
@if (isSelectedRowsOfType(LdapEntryType.Group)) {
14+
<div class="context-item" (click)="openRenameGroupDialog()">
15+
{{ 'catalog-content.rename' | transloco }}
16+
</div>
17+
}
718

819
@if (isSelectedRowsOfType(LdapEntryType.User)) {
920
<div class="context-item" (click)="openChangePasswordDialog()">

projects/multidirectory-app/src/app/components/modals/components/core/context-menu/context-menu.component.ts

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@ import { DeleteEntryRequest } from '@models/api/entry/delete-request';
1212
import { UpdateEntryResponse } from '@models/api/entry/update-response';
1313
import { LdapEntryType } from '@models/core/ldap/ldap-entry-type';
1414
import { NavigationNode } from '@models/core/navigation/navigation-node';
15-
import { AppNavigationService } from '@services/app-navigation.service';
1615
import { AppSettingsService } from '@services/app-settings.service';
1716
import { BulkService } from '@services/bulk.service';
1817
import { LdapTreeviewService } from '@services/ldap/ldap-treeview.service';
1918
import { MultidirectoryApiService } from '@services/multidirectory-api.service';
2019
import { MultidirectoryUiKitModule } from 'multidirectory-ui-kit';
21-
import { concat, EMPTY, of, switchMap, take, tap } from 'rxjs';
20+
import { concat, EMPTY, filter, of, switchMap, take, tap } from 'rxjs';
2221
import { ChangePasswordDialogData, ChangePasswordDialogReturnData } from '../../../interfaces/change-password-dialog.interface';
2322
import { ConfirmDeleteDialogData, ConfirmDeleteDialogReturnData } from '../../../interfaces/confirm-delete-dialog.interface';
2423
import { ConfirmDialogData, ConfirmDialogReturnData } from '../../../interfaces/confirm-dialog.interface';
@@ -30,9 +29,16 @@ import { DialogService } from '../../../services/dialog.service';
3029
import { ChangePasswordDialogComponent } from '../../dialogs/change-password-dialog/change-password-dialog.component';
3130
import { ConfirmDeleteDialogComponent } from '../../dialogs/confirm-delete-dialog/confirm-delete-dialog.component';
3231
import { ConfirmDialogComponent } from '../../dialogs/confirm-dialog/confirm-dialog.component';
33-
import { EntityPropertiesDialogComponent } from '../../dialogs/entity-properties-dialog/entity-properties-dialog.component';
3432
import { MoveEntityDialogComponent } from '../../dialogs/move-entity-dialog/move-entity-dialog.component';
3533
import { ToastrService } from 'ngx-toastr';
34+
import { RenameUserDialogComponent } from '@components/modals/components/dialogs/rename-user-dialog/rename-user-dialog.component';
35+
import { RenameGroupDialogComponent } from '@components/modals/components/dialogs/rename-group-dialog/rename-group-dialog.component';
36+
import { LdapRenameRequest } from '@core/ldap/ldap-rename.request';
37+
import { Group } from '@core/groups/group';
38+
import { DataBusService } from '@services/data-bus.service';
39+
import {
40+
EntityPropertiesDialogComponent
41+
} from '@components/modals/components/dialogs/entity-properties-dialog/entity-properties-dialog.component';
3642

3743
@Component({
3844
selector: 'app-context-menu',
@@ -53,9 +59,9 @@ export class ContextMenuComponent implements OnInit {
5359
private api: MultidirectoryApiService = inject(MultidirectoryApiService);
5460
private appSettings: AppSettingsService = inject(AppSettingsService);
5561
private ldapTreeview = inject(LdapTreeviewService);
56-
private navigation = inject(AppNavigationService);
5762
private bulk: BulkService<NavigationNode> = inject(BulkService<NavigationNode>);
5863
private toastr = inject(ToastrService);
64+
private dataBus = inject(DataBusService);
5965
private getAccessorStrategy: GetAccessorStrategy = inject(GetAccessorStrategy);
6066
private completeUpdateEntiresStrategy: CompleteUpdateEntiresStrategies = inject(CompleteUpdateEntiresStrategies);
6167
private contextMenuData: ContextMenuData = inject(DIALOG_DATA);
@@ -141,7 +147,7 @@ export class ContextMenuComponent implements OnInit {
141147
},
142148
}).closed;
143149
}),
144-
tap(() => this.navigation.reload()),
150+
tap(() => this.dataBus.emitUpdateGridContent()),
145151
),
146152
);
147153
}
@@ -157,8 +163,8 @@ export class ContextMenuComponent implements OnInit {
157163
},
158164
})
159165
.closed.pipe(switchMap((x) => (x ? this.api.updateManyDn(x) : EMPTY)))
160-
.subscribe((x) => {
161-
this.navigation.reload();
166+
.subscribe(() => {
167+
this.dataBus.emitUpdateGridContent();
162168
});
163169
}
164170

@@ -174,24 +180,24 @@ export class ContextMenuComponent implements OnInit {
174180
},
175181
})
176182
.closed.pipe(
177-
switchMap((isConfirmed) => {
178-
return isConfirmed
179-
? concat(
180-
...this.entries.map((x) =>
181-
// TODO: Будет deleteMany
182-
this.api.delete(
183-
new DeleteEntryRequest({
184-
entry: x.id,
185-
}),
186-
),
187-
),
188-
)
189-
: of(null);
190-
}),
191-
)
183+
switchMap((isConfirmed) => {
184+
return isConfirmed
185+
? concat(
186+
...this.entries.map((x) =>
187+
// TODO: Будет deleteMany
188+
this.api.delete(
189+
new DeleteEntryRequest({
190+
entry: x.id,
191+
}),
192+
),
193+
),
194+
)
195+
: of(null);
196+
}),
197+
)
192198
.subscribe((result) => {
193199
this.ldapTreeview.invalidate(toDeleteDNs);
194-
this.navigation.reload();
200+
this.dataBus.emitUpdateGridContent();
195201
});
196202
}
197203

@@ -228,4 +234,48 @@ export class ContextMenuComponent implements OnInit {
228234

229235
dropdownEl.style.transform = `translate(${offsetX < 0 ? offsetX : 0}px, ${offsetY < 0 ? offsetY : 0}px)`;
230236
}
237+
238+
protected openRenameUserDialog() {
239+
this.contextMenuService.close(null);
240+
const id = this.entries[0].id;
241+
242+
this.dialogService
243+
.open<LdapRenameRequest, string, RenameUserDialogComponent>({
244+
component: RenameUserDialogComponent,
245+
dialogConfig: {
246+
minHeight: '230px',
247+
data: id,
248+
},
249+
})
250+
.closed.pipe(
251+
filter(Boolean),
252+
switchMap((request) => this.api.rename(request)),
253+
)
254+
.subscribe(() => {
255+
this.toastr.success(translate('rename-user-dialog.success'));
256+
this.dataBus.emitUpdateGridContent();
257+
});
258+
}
259+
260+
protected openRenameGroupDialog() {
261+
this.contextMenuService.close(null);
262+
const group = this.entries[0] as Group;
263+
264+
this.dialogService
265+
.open<LdapRenameRequest, Group, RenameGroupDialogComponent>({
266+
component: RenameGroupDialogComponent,
267+
dialogConfig: {
268+
minHeight: '230px',
269+
data: group,
270+
},
271+
})
272+
.closed.pipe(
273+
filter(Boolean),
274+
switchMap((request) => this.api.rename(request)),
275+
)
276+
.subscribe(() => {
277+
this.toastr.success(translate('rename-group-dialog.success'));
278+
this.dataBus.emitUpdateGridContent();
279+
});
280+
}
231281
}

projects/multidirectory-app/src/app/components/modals/components/dialogs/entity-properties-dialog/entity-properties-dialog.component.ts

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit, ViewChild } from '@angular/core';
22
import { ComputerPropertiesComponent } from '@features/ldap-properties/computer-properties/computer-properties.component';
3-
import { DialogComponent } from '../../core/dialog/dialog.component';
43
import { EntityAttributesComponent } from '@features/entity-attributes/entity-attributes.component';
54
import { GroupPropertiesComponent } from '@features/ldap-properties/group-properties/group-properties.component';
65
import { MultidirectoryUiKitModule } from 'multidirectory-ui-kit';
76
import { translate, TranslocoPipe } from '@jsverse/transloco';
87
import { UserPropertiesComponent } from '@features/ldap-properties/user-properties/user-properties.component';
98
import { EMPTY, finalize, of, switchMap, take } from 'rxjs';
10-
import { DialogService } from '../../../services/dialog.service';
119
import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
12-
import { EntityPropertiesDialogData, EntityPropertiesDialogReturnData } from '../../../interfaces/entity-properties-dialog.interface';
1310
import { AttributeService } from '@services/attributes.service';
1411
import { MultidirectoryApiService } from '@services/multidirectory-api.service';
15-
import { ConfirmDialogData, ConfirmDialogReturnData } from '../../../interfaces/confirm-dialog.interface';
16-
import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component';
1712
import { LdapAttributes } from '@core/ldap/ldap-attributes/ldap-attributes';
1813
import { SearchQueries } from '@core/ldap/search';
1914
import { LdapEntryType } from '@models/core/ldap/ldap-entry-type';
15+
import { DialogComponent } from '@components/modals/components/core/dialog/dialog.component';
16+
import { ConfirmDialogComponent } from '@components/modals/components/dialogs/confirm-dialog/confirm-dialog.component';
17+
import { ConfirmDialogData, ConfirmDialogReturnData } from '@components/modals/interfaces/confirm-dialog.interface';
18+
import {
19+
EntityPropertiesDialogData,
20+
EntityPropertiesDialogReturnData,
21+
} from '@components/modals/interfaces/entity-properties-dialog.interface';
22+
import { DialogService } from '@components/modals/services/dialog.service';
23+
import { AppWindowsService } from '@services/app-windows.service';
2024
import { ContactPropertiesComponent } from '@features/ldap-properties/contact-properties/contact-properties.component';
25+
import { DataBusService } from '@services/data-bus.service';
2126

2227
@Component({
2328
selector: 'app-entity-properties-dialog',
@@ -46,22 +51,30 @@ export class EntityPropertiesDialogComponent implements OnInit {
4651
EntityTypes = LdapEntryType;
4752
entityType: LdapEntryType = this.dialogData.entity.type;
4853
accessor: LdapAttributes = new LdapAttributes([]);
49-
54+
loaded = false;
55+
private readonly windows = inject(AppWindowsService);
5056
private dialogService: DialogService = inject(DialogService);
5157
private dialogRef: DialogRef<EntityPropertiesDialogReturnData, EntityPropertiesDialogComponent> = inject(DialogRef);
5258
private attributeService: AttributeService = inject(AttributeService);
5359
private api: MultidirectoryApiService = inject(MultidirectoryApiService);
60+
private dataBus = inject(DataBusService);
5461
private cdr: ChangeDetectorRef = inject(ChangeDetectorRef);
5562

5663
ngOnInit(): void {
64+
this.loaded = false;
65+
this.windows.showSpinner();
5766
this.api
5867
.search(SearchQueries.getProperites(this.dialogData.entity.id))
59-
.pipe(take(1))
68+
.pipe(
69+
take(1),
70+
finalize(() => {
71+
this.windows.hideSpinner();
72+
}),
73+
)
6074
.subscribe((props) => {
6175
const attributes = props.search_result[0].partial_attributes;
62-
76+
this.loaded = true;
6377
this.accessor = this.attributeService.getTrackableAttributes(this.dialogData.entity, new LdapAttributes(attributes));
64-
6578
this.cdr.detectChanges();
6679
});
6780
}
@@ -73,8 +86,9 @@ export class EntityPropertiesDialogComponent implements OnInit {
7386
save(needConfirmation = false) {
7487
this.dialogComponent.showSpinner();
7588
const updateRequest = this.attributeService.createAttributeUpdateRequest(this.accessor);
89+
const hasChanges = updateRequest.changes.length > 0;
7690

77-
if (updateRequest.changes.length === 0 || (this.userProps && !this.userProps.generalPropertiesValid)) {
91+
if (!hasChanges || (this.userProps && !this.userProps.generalPropertiesValid)) {
7892
this.dialogComponent.hideSpinner();
7993
this.close();
8094

@@ -84,20 +98,20 @@ export class EntityPropertiesDialogComponent implements OnInit {
8498
const confirmObservable$ = !needConfirmation
8599
? of<ConfirmDialogReturnData>(true)
86100
: this.dialogService.open<ConfirmDialogReturnData, ConfirmDialogData, ConfirmDialogComponent>({
87-
component: ConfirmDialogComponent,
88-
dialogConfig: {
89-
minHeight: '160px',
90-
data: {
91-
promptHeader: translate('confirmation-dialog.prompt-header'),
92-
promptText: translate('confirmation-dialog.prompt-text'),
93-
primaryButtons: [{ id: true, text: translate('confirmation-dialog.yes') }],
94-
secondaryButtons: [
95-
{ id: false, text: translate('confirmation-dialog.no') },
96-
{ id: 'cancel', text: translate('confirmation-dialog.cancel') },
97-
],
98-
},
101+
component: ConfirmDialogComponent,
102+
dialogConfig: {
103+
minHeight: '160px',
104+
data: {
105+
promptHeader: translate('confirmation-dialog.prompt-header'),
106+
promptText: translate('confirmation-dialog.prompt-text'),
107+
primaryButtons: [{ id: true, text: translate('confirmation-dialog.yes') }],
108+
secondaryButtons: [
109+
{ id: false, text: translate('confirmation-dialog.no') },
110+
{ id: 'cancel', text: translate('confirmation-dialog.cancel') },
111+
],
99112
},
100-
}).closed;
113+
},
114+
}).closed;
101115

102116
confirmObservable$
103117
.pipe(
@@ -120,6 +134,10 @@ export class EntityPropertiesDialogComponent implements OnInit {
120134
)
121135
.subscribe({
122136
next: () => {
137+
if (hasChanges) {
138+
this.dataBus.emitUpdateGridContent();
139+
}
140+
123141
this.close();
124142
},
125143
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<app-dialog>
2+
<div class="app-modal-header">{{ 'rename-group-dialog.header' | transloco }}</div>
3+
4+
<div class="form-container" [formGroup]="form" (ngSubmit)="onSubmit()">
5+
<div>
6+
<label for="groupName" class="col-span-1"> {{ 'group-create.group-name' | transloco }}</label>
7+
<md-textbox class="row-1 col-2" formControlName="groupName"></md-textbox>
8+
</div>
9+
</div>
10+
11+
<div class="app-modal-footer flex justify-end gap-1">
12+
<md-button (click)="cancel()">{{ 'rename-group-dialog.cancel' | transloco }}</md-button>
13+
<md-button (click)="onSubmit()" [disabled]="!form.valid"
14+
[primary]="true">{{ 'rename-group-dialog.apply' | transloco }}
15+
</md-button>
16+
</div>
17+
</app-dialog>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.form-container {
2+
display: flex;
3+
flex-direction: column;
4+
row-gap: 1rem;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
2+
import { Component, inject } from '@angular/core';
3+
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
4+
import { TranslocoModule } from '@jsverse/transloco';
5+
import { MultidirectoryUiKitModule } from 'multidirectory-ui-kit';
6+
import { DialogService } from '../../../services/dialog.service';
7+
import { DialogComponent } from '../../core/dialog/dialog.component';
8+
import { Group } from '@core/groups/group';
9+
import { LdapModificationRecord, LdapRenameRequest } from '@core/ldap/ldap-rename.request';
10+
11+
@Component({
12+
selector: 'app-rename-group-dialog',
13+
templateUrl: './rename-group-dialog.component.html',
14+
styleUrls: ['./rename-group-dialog.component.scss'],
15+
imports: [DialogComponent, ReactiveFormsModule, TranslocoModule, MultidirectoryUiKitModule],
16+
})
17+
export class RenameGroupDialogComponent {
18+
private dialogRef = inject(DialogRef);
19+
private dialog = inject(DialogService);
20+
private group: Group = inject(DIALOG_DATA);
21+
private fb = inject(FormBuilder);
22+
23+
protected form = this.fb.nonNullable.group({
24+
groupName: [this.group.name, [Validators.required]],
25+
});
26+
27+
protected onSubmit() {
28+
this.dialog.close(this.dialogRef, this.buildRenameRequest());
29+
}
30+
31+
private buildRenameRequest(): LdapRenameRequest {
32+
const groupName = this.form.controls.groupName.getRawValue();
33+
const groupDn = this.group.dn;
34+
35+
if (!groupName || !groupDn) {
36+
return {} as LdapRenameRequest;
37+
}
38+
39+
const newCn = `cn=${groupName}`;
40+
41+
return {
42+
object: groupDn,
43+
newrdn: newCn,
44+
changes: [
45+
new LdapModificationRecord(2, { type: 'sAMAccountName', vals: [groupName] }),
46+
new LdapModificationRecord(2, { type: 'name', vals: [groupName] }),
47+
],
48+
};
49+
}
50+
51+
protected cancel() {
52+
this.dialog.close(this.dialogRef, null);
53+
}
54+
}

0 commit comments

Comments
 (0)