Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CCUI.DAPPI/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"proxyConfig": "proxy.conf.json"
},
"configurations": {
"production": {
"buildTarget": "CCUI.DAPPI:build:production"
Expand Down
8 changes: 8 additions & 0 deletions CCUI.DAPPI/proxy.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"/api": {
"target": "http://localhost:5149",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, ReactiveFormsModule, ValidatorFn, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { UsersManagementService } from '../services/auth/users-management.service';
import { UsersAndPermissionsPluginService } from '../services/auth/users-and-permissions-plugin.service';

export function passwordMatchValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
Expand All @@ -27,14 +28,16 @@ export function passwordMatchValidator(): ValidatorFn {
export class CompleteInvitationComponent implements OnInit {
form: FormGroup;
token = '';
flow = '';
isSubmitting = false;
errorMessage = '';

constructor(
private fb: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private usersManagementService: UsersManagementService
private usersManagementService: UsersManagementService,
private usersAndPermissionsPluginService: UsersAndPermissionsPluginService
) {
this.form = this.fb.group(
{
Expand All @@ -48,6 +51,7 @@ export class CompleteInvitationComponent implements OnInit {

ngOnInit(): void {
this.token = this.route.snapshot.queryParamMap.get('token') ?? '';
this.flow = (this.route.snapshot.queryParamMap.get('flow') ?? '').toLowerCase();

if (!this.token) {
this.errorMessage = 'Invitation token is missing.';
Expand All @@ -71,7 +75,11 @@ export class CompleteInvitationComponent implements OnInit {
const oldPassword = this.form.get('oldPassword')?.value;
const newPassword = this.form.get('newPassword')?.value;

this.usersManagementService.completeInvitation({ token: this.token, oldPassword, newPassword }).subscribe({
const completeInvitation$ = this.flow === 'usersandpermissions'
? this.usersAndPermissionsPluginService.completeInvitation({ token: this.token, oldPassword, newPassword })
: this.usersManagementService.completeInvitation({ token: this.token, oldPassword, newPassword });

completeInvitation$.subscribe({
next: () => {
this.isSubmitting = false;
this.router.navigate(['/auth'], { queryParams: { invitationCompleted: 'true' } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { MatSelectModule } from '@angular/material/select';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RolesManagementService, RoleItem } from '../services/auth/roles-management.service';
import { UsersAndPermissionsPluginService } from '../services/auth/users-and-permissions-plugin.service';

export interface InviteUserData {
username: string;
Expand All @@ -18,6 +19,7 @@ export interface InviteUserData {

export interface InviteUserDialogConfig {
isEmailServiceAvailable: boolean;
usePluginRoles?: boolean;
}

@Component({
Expand Down Expand Up @@ -47,20 +49,17 @@ export class InviteUserDialogComponent implements OnInit {
constructor(
private dialogRef: MatDialogRef<InviteUserDialogComponent>,
private rolesManagementService: RolesManagementService,
private usersAndPermissionsPluginService: UsersAndPermissionsPluginService,
@Inject(MAT_DIALOG_DATA) public data: InviteUserDialogConfig,
) {}

ngOnInit(): void {
this.rolesLoading = true;
this.rolesManagementService.getRoles().subscribe({
next: (res) => {
this.availableRoles = res.Data;
this.rolesLoading = false;
},
error: () => {
this.rolesLoading = false;
},
});
if (this.data.usePluginRoles) {
this.loadPluginRoles();
return;
}

this.loadRegularRoles();
}

get isValid(): boolean {
Expand All @@ -84,4 +83,34 @@ export class InviteUserDialogComponent implements OnInit {
onCancel(): void {
this.dialogRef.close(null);
}

private loadRegularRoles(): void {
this.rolesLoading = true;
this.rolesManagementService.getRoles().subscribe({
next: (res) => {
this.availableRoles = res.Data;
this.rolesLoading = false;
},
error: () => {
this.rolesLoading = false;
},
});
}

private loadPluginRoles(): void {
this.rolesLoading = true;
this.usersAndPermissionsPluginService.getAllRoles().subscribe({
next: (roles) => {
this.availableRoles = roles.map((role) => ({
Id: role.id,
Name: role.name,
UserCount: 0,
}));
this.rolesLoading = false;
},
error: () => {
this.rolesLoading = false;
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { map, Observable } from 'rxjs';
import { BASE_API_URL } from '../../../Constants';
import { CompleteInvitationRequest, InviteUserRequest } from './users-management.service';

export interface UsersAndPermissionsRoleItem {
id: string;
name: string;
isDefaultForAuthenticatedUser: boolean;
}

export interface UsersAndPermissionsUserItem {
id: number;
userName: string;
email: string;
emailConfirmed: boolean;
acceptedInvitation?: boolean;
roleId: number;
roleName: string;
}

export interface UsersAndPermissionsPermissionItem {
permissionName: string;
description: string;
Expand All @@ -24,14 +35,26 @@ export type UsersAndPermissionsRolePermissionsResponse = Record<
export class UsersAndPermissionsPluginService {
private readonly endpoint = 'usersandpermissions';

constructor(private http: HttpClient) {}
constructor(private http: HttpClient) { }

getAllRoles(): Observable<UsersAndPermissionsRoleItem[]> {
return this.http.get<any[]>(`${BASE_API_URL}${this.endpoint}/roles`).pipe(
map((roles) => (roles ?? []).map((role) => this.normalizeRole(role)))
);
}

getAllUsers(): Observable<UsersAndPermissionsUserItem[]> {
return this.http.get<any[]>(`${BASE_API_URL}${this.endpoint}/users`).pipe(
map((users) => (users ?? []).map((user) => this.normalizeUser(user)))
);
}

deleteUser(userId: number): Observable<{ message: string }> {
return this.http.delete<{ message: string }>(
`${BASE_API_URL}${this.endpoint}/users/${userId}`
);
}

getRolePermissions(roleName: string): Observable<UsersAndPermissionsRolePermissionsResponse> {
const params = new HttpParams().set('roleName', roleName);

Expand All @@ -47,6 +70,19 @@ export class UsersAndPermissionsPluginService {
);
}

inviteUser(data: InviteUserRequest): Observable<UsersAndPermissionsUserItem> {
return this.http
.post<any>(`${BASE_API_URL}${this.endpoint}`, data)
.pipe(map((user) => this.normalizeUser(user)));
}

completeInvitation(data: CompleteInvitationRequest): Observable<{ message: string }> {
return this.http.post<{ message: string }>(
`${BASE_API_URL}${this.endpoint}/complete-invitation`,
data
);
}

private normalizeRole(role: any): UsersAndPermissionsRoleItem {
return {
id: role?.id ?? role?.Id ?? '',
Expand All @@ -63,4 +99,16 @@ export class UsersAndPermissionsPluginService {
selected: permission?.selected ?? permission?.Selected ?? false,
};
}

private normalizeUser(user: any): UsersAndPermissionsUserItem {
return {
id: user?.id ?? user?.Id ?? 0,
userName: user?.userName ?? user?.UserName ?? '',
email: user?.email ?? user?.Email ?? '',
emailConfirmed: user?.emailConfirmed ?? user?.EmailConfirmed ?? false,
acceptedInvitation: user?.acceptedInvitation ?? user?.AcceptedInvitation ?? false,
roleId: user?.roleId ?? user?.RoleId ?? 0,
roleName: user?.roleName ?? user?.RoleName ?? '',
};
}
}
16 changes: 14 additions & 2 deletions CCUI.DAPPI/src/app/settings/settings.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,20 @@
class="settings-sidebar__item"
[class.settings-sidebar__item--active]="activeTab === 'usersAndPermissionsPlugin'"
(click)="selectTab('usersAndPermissionsPlugin')"
aria-label="Users and permissions plugin"
aria-label="Roles and permissions"
>
<mat-icon>shield</mat-icon>
<span>Users & Permissions plugin</span>
<span>Roles & Permissions</span>
</button>

<button
class="settings-sidebar__item"
[class.settings-sidebar__item--active]="activeTab === 'usersAndPermissionsPluginUsers'"
(click)="selectTab('usersAndPermissionsPluginUsers')"
aria-label="Users and permissions users"
>
<mat-icon>people</mat-icon>
<span>Users</span>
</button>
}

Expand Down Expand Up @@ -59,6 +69,8 @@
<div class="container">
@if (activeTab === 'storage') {
<app-data-storage></app-data-storage>
} @else if (activeTab === 'usersAndPermissionsPluginUsers') {
<ng-container [ngComponentOutlet]="usersAndPermissionsUsersComponent"></ng-container>
} @else if (activeTab === 'usersAndPermissionsPlugin') {
<header class="plugin-header">
<div class="plugin-title-block">
Expand Down
19 changes: 16 additions & 3 deletions CCUI.DAPPI/src/app/settings/settings.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
Expand All @@ -7,6 +8,7 @@ import { Subscription } from 'rxjs';
import { DataStorageComponent } from './data-storage/data-storage.component';
import { UsersComponent } from './users/users.component';
import { RolesComponent } from './roles/roles.component';
import { UsersAndPermissionsUsersComponent } from './users-and-permissions-plugin-users/users-and-permissions-plugin-users.component';
import { UsersManagementService } from '../services/auth/users-management.service';
import {
UsersAndPermissionsPluginService,
Expand All @@ -26,12 +28,13 @@ interface ControllerPermissionGroup {
allowedCount: number;
}

type SettingsTab = 'storage' | 'users' | 'roles' | 'usersAndPermissionsPlugin';
type SettingsTab = 'storage' | 'users' | 'roles' | 'usersAndPermissionsPlugin' | 'usersAndPermissionsPluginUsers';

@Component({
selector: 'app-settings',
standalone: true,
imports: [
CommonModule,
MatButtonToggleModule,
MatIconModule,
MatProgressSpinnerModule,
Expand All @@ -45,6 +48,7 @@ type SettingsTab = 'storage' | 'users' | 'roles' | 'usersAndPermissionsPlugin';
})
export class SettingsComponent implements OnInit, OnDestroy {
private subscription = new Subscription();
readonly usersAndPermissionsUsersComponent = UsersAndPermissionsUsersComponent;

activeTab: SettingsTab = 'storage';
usersAndPermissionsEnabled = false;
Expand Down Expand Up @@ -75,7 +79,10 @@ export class SettingsComponent implements OnInit, OnDestroy {
}

selectTab(tab: SettingsTab): void {
if (tab === 'usersAndPermissionsPlugin' && !this.usersAndPermissionsEnabled) {
if (
(tab === 'usersAndPermissionsPlugin' || tab === 'usersAndPermissionsPluginUsers')
&& !this.usersAndPermissionsEnabled
) {
return;
}

Expand All @@ -92,7 +99,13 @@ export class SettingsComponent implements OnInit, OnDestroy {
next: (response) => {
this.usersAndPermissionsEnabled = !!response.services?.['usersAndPermissions'];

if (!this.usersAndPermissionsEnabled && this.activeTab === 'usersAndPermissionsPlugin') {
if (
!this.usersAndPermissionsEnabled
&& (
this.activeTab === 'usersAndPermissionsPlugin'
|| this.activeTab === 'usersAndPermissionsPluginUsers'
)
) {
this.activeTab = 'storage';
}
},
Expand Down
Loading
Loading