Skip to content

Commit 7fce853

Browse files
committed
feat(admin): add consent hubs page
* Added new consent hub page to Perun admin. * Consent hubs page shows a list of all consent hubs. * Each consent hub row contains a slide toggle for enforce consents flag. This toggle has on click event, which opens a dialog. Through this dialog, perun admin can change this flag.
1 parent 1a091d9 commit 7fce853

16 files changed

+478
-1
lines changed

apps/admin-gui/src/app/admin/admin-routing.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { AdminOwnersComponent } from './pages/admin-page/admin-owners/admin-owne
3030
import { UserFacilitiesComponent } from '../users/pages/user-detail-page/user-facilities/user-facilities.component';
3131
import { UserAccountsComponent } from '../users/pages/user-detail-page/user-accounts/user-accounts.component';
3232
import { AdminAuditLogComponent } from './pages/admin-page/admin-audit-log/admin-audit-log.component';
33+
import { AdminConsentHubsComponent } from './pages/admin-page/admin-consent-hubs/admin-consent-hubs.component';
3334

3435
const routes: Routes = [
3536
{
@@ -97,6 +98,11 @@ const routes: Routes = [
9798
component: AdminExtSourcesComponent,
9899
data: { animation: 'AdminExtSourcesPage' },
99100
},
101+
{
102+
path: 'consent_hubs',
103+
component: AdminConsentHubsComponent,
104+
data: { animation: 'AdminConsentHubsPage' },
105+
},
100106
],
101107
},
102108
{

apps/admin-gui/src/app/admin/admin.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { UsersModule } from '../users/users.module';
2626
import { ServiceDestinationsComponent } from './pages/admin-page/admin-services/service-detail-page/service-destinations/service-destinations.component';
2727
import { AdminOwnersComponent } from './pages/admin-page/admin-owners/admin-owners.component';
2828
import { AdminAuditLogComponent } from './pages/admin-page/admin-audit-log/admin-audit-log.component';
29+
import { AdminConsentHubsComponent } from './pages/admin-page/admin-consent-hubs/admin-consent-hubs.component';
2930

3031
@NgModule({
3132
declarations: [
@@ -47,6 +48,7 @@ import { AdminAuditLogComponent } from './pages/admin-page/admin-audit-log/admin
4748
ServiceDestinationsComponent,
4849
AdminOwnersComponent,
4950
AdminAuditLogComponent,
51+
AdminConsentHubsComponent,
5052
],
5153
imports: [
5254
NgxGraphModule,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<div>
2+
<h1 class="page-subtitle">{{'ADMIN.CONSENT_HUBS.TITLE' | translate}}</h1>
3+
4+
<perun-web-apps-refresh-button (refresh)="refreshTable()"></perun-web-apps-refresh-button>
5+
<perun-web-apps-immediate-filter
6+
[placeholder]="'ADMIN.CONSENT_HUBS.SEARCH'"
7+
(filter)="applyFilter($event)">
8+
</perun-web-apps-immediate-filter>
9+
10+
<mat-spinner class="ml-auto mr-auto" *ngIf="loading"></mat-spinner>
11+
<app-perun-web-apps-consent-hubs-list
12+
*ngIf="!loading"
13+
[consentHubs]="consentHubs"
14+
[filterValue]="filterValue"
15+
[tableId]="tableId">
16+
</app-perun-web-apps-consent-hubs-list>
17+
</div>

apps/admin-gui/src/app/admin/pages/admin-page/admin-consent-hubs/admin-consent-hubs.component.scss

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { ConsentHub, ConsentsManagerService } from '@perun-web-apps/perun/openapi';
3+
import { TABLE_CONSENT_HUBS } from '@perun-web-apps/config/table-config';
4+
5+
@Component({
6+
selector: 'app-perun-web-apps-admin-consent-hubs',
7+
templateUrl: './admin-consent-hubs.component.html',
8+
styleUrls: ['./admin-consent-hubs.component.scss'],
9+
})
10+
export class AdminConsentHubsComponent implements OnInit {
11+
constructor(private consentsManager: ConsentsManagerService) {}
12+
13+
loading = false;
14+
tableId = TABLE_CONSENT_HUBS;
15+
filterValue = '';
16+
consentHubs: ConsentHub[] = [];
17+
18+
ngOnInit(): void {
19+
this.refreshTable();
20+
}
21+
22+
applyFilter(filterValue: string): void {
23+
this.filterValue = filterValue;
24+
}
25+
26+
refreshTable(): void {
27+
this.loading = true;
28+
29+
this.consentsManager.getAllConsentHubs().subscribe((consentHubs) => {
30+
this.consentHubs = consentHubs;
31+
this.loading = false;
32+
});
33+
}
34+
}

apps/admin-gui/src/app/admin/pages/admin-page/admin-overview/admin-overview.component.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,11 @@ export class AdminOverviewComponent {
5454
label: 'MENU_ITEMS.ADMIN.AUDIT_LOG',
5555
style: 'admin-btn',
5656
},
57+
{
58+
cssIcon: 'perun-consent-hubs',
59+
url: '/admin/consent_hubs',
60+
label: 'MENU_ITEMS.ADMIN.CONSENT_HUBS',
61+
style: 'admin-btn',
62+
},
5763
];
5864
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<div [hidden]="consentHubs.length === 0 || dataSource.filteredData.length === 0" class="card mt-2">
2+
<perun-web-apps-table-wrapper
3+
(exportData)="exportData($event)"
4+
[tableId]="tableId"
5+
[dataLength]="dataSource.filteredData.length"
6+
[pageSizeOptions]="pageSizeOptions">
7+
<table
8+
[dataSource]="dataSource"
9+
class="w-100"
10+
mat-table
11+
matSort
12+
matSortActive="id"
13+
matSortDirection="asc"
14+
matSortDisableClear>
15+
<ng-container matColumnDef="id">
16+
<th *matHeaderCellDef mat-header-cell mat-sort-header>
17+
{{'SHARED.COMPONENTS.CONSENT_HUBS_LIST.ID' | translate}}
18+
</th>
19+
<td *matCellDef="let consentHub" class="static-column-size" mat-cell>{{consentHub.id}}</td>
20+
</ng-container>
21+
<ng-container matColumnDef="name">
22+
<th *matHeaderCellDef mat-header-cell mat-sort-header>
23+
{{'SHARED.COMPONENTS.CONSENT_HUBS_LIST.NAME' | translate}}
24+
</th>
25+
<td *matCellDef="let consentHub" mat-cell>{{consentHub.name}}</td>
26+
</ng-container>
27+
<ng-container matColumnDef="enforceConsents">
28+
<th *matHeaderCellDef mat-header-cell mat-sort-header>
29+
{{'SHARED.COMPONENTS.CONSENT_HUBS_LIST.ENFORCE_CONSENTS' | translate}}
30+
</th>
31+
<td *matCellDef="let consentHub" mat-cell>
32+
<mat-slide-toggle
33+
(change)="changeEnforceFlag(consentHub, $event)"
34+
[checked]="consentHub.enforceConsents">
35+
</mat-slide-toggle>
36+
</td>
37+
</ng-container>
38+
<ng-container matColumnDef="facilities">
39+
<th *matHeaderCellDef mat-header-cell mat-sort-header>
40+
{{'SHARED.COMPONENTS.CONSENT_HUBS_LIST.FACILITIES' | translate}}
41+
</th>
42+
<td *matCellDef="let consentHub" mat-cell>
43+
<span *ngFor="let facility of consentHub.facilities">
44+
{{facility.name}}
45+
<span class="text-muted"> #{{facility.id}}</span>
46+
<br />
47+
</span>
48+
</td>
49+
</ng-container>
50+
51+
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
52+
<tr
53+
*matRowDef="let consentHub; columns: displayedColumns;"
54+
class="dark-hover-list-item"
55+
mat-row></tr>
56+
</table>
57+
</perun-web-apps-table-wrapper>
58+
</div>
59+
60+
<app-alert *ngIf="consentHubs.length === 0" alert_type="warn">
61+
{{'SHARED.COMPONENTS.CONSENT_HUBS_LIST.NO_CONSENT_HUBS' | translate}}
62+
</app-alert>
63+
64+
<app-alert
65+
*ngIf="dataSource.filteredData.length === 0 && consentHubs.length !== 0"
66+
alert_type="warn">
67+
{{'SHARED.COMPONENTS.CONSENT_HUBS_LIST.NO_FILTER_RESULTS' | translate}}
68+
</app-alert>

apps/admin-gui/src/app/shared/components/consent-hubs-list/consent-hubs-list.component.scss

Whitespace-only changes.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { Component, Input, OnChanges, ViewChild } from '@angular/core';
2+
import { ConsentHub, ConsentsManagerService } from '@perun-web-apps/perun/openapi';
3+
import {
4+
customDataSourceFilterPredicate,
5+
customDataSourceSort,
6+
downloadData,
7+
getDataForExport,
8+
getDefaultDialogConfig,
9+
TABLE_ITEMS_COUNT_OPTIONS,
10+
TableWrapperComponent,
11+
} from '@perun-web-apps/perun/utils';
12+
import { MatSort } from '@angular/material/sort';
13+
import { MatTableDataSource } from '@angular/material/table';
14+
import { NotificatorService, TableCheckbox } from '@perun-web-apps/perun/services';
15+
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
16+
import { MatDialog } from '@angular/material/dialog';
17+
import { TranslateService } from '@ngx-translate/core';
18+
import { EditEnforceConsentsDialogComponent } from '../dialogs/edit-enforce-consents-dialog/edit-enforce-consents-dialog.component';
19+
20+
@Component({
21+
selector: 'app-perun-web-apps-consent-hubs-list',
22+
templateUrl: './consent-hubs-list.component.html',
23+
styleUrls: ['./consent-hubs-list.component.scss'],
24+
})
25+
export class ConsentHubsListComponent implements OnChanges {
26+
constructor(
27+
private tableCheckbox: TableCheckbox,
28+
private dialog: MatDialog,
29+
private notificator: NotificatorService,
30+
private translate: TranslateService,
31+
private consentsManager: ConsentsManagerService
32+
) {}
33+
34+
@Input() consentHubs: ConsentHub[];
35+
@Input() filterValue = '';
36+
@Input() displayedColumns: string[] = ['id', 'name', 'enforceConsents', 'facilities'];
37+
@Input() tableId: string;
38+
39+
@ViewChild(TableWrapperComponent, { static: true }) child: TableWrapperComponent;
40+
41+
@ViewChild(MatSort, { static: true }) set matSort(ms: MatSort) {
42+
this.sort = ms;
43+
this.setDataSource();
44+
}
45+
46+
private sort: MatSort;
47+
48+
dataSource: MatTableDataSource<ConsentHub>;
49+
exporting = false;
50+
pageSizeOptions = TABLE_ITEMS_COUNT_OPTIONS;
51+
52+
ngOnChanges(): void {
53+
this.dataSource = new MatTableDataSource<ConsentHub>(this.consentHubs);
54+
this.setDataSource();
55+
}
56+
57+
getDataForColumn(data: ConsentHub, column: string): string {
58+
switch (column) {
59+
case 'id':
60+
return data.id.toString();
61+
case 'name':
62+
return data.name;
63+
case 'enforceConsents':
64+
return data.enforceConsents ? 'true' : 'false';
65+
case 'facilities': {
66+
let result = '';
67+
data.facilities.forEach((f) => (result += f.name + ' #' + f.id + ';'));
68+
return result.slice(0, -1);
69+
}
70+
default:
71+
return '';
72+
}
73+
}
74+
75+
exportData(format: string): void {
76+
downloadData(
77+
getDataForExport(
78+
this.dataSource.filteredData,
79+
this.displayedColumns,
80+
this.getDataForColumn,
81+
this
82+
),
83+
format
84+
);
85+
}
86+
87+
setDataSource(): void {
88+
if (this.dataSource) {
89+
this.dataSource.filterPredicate = (data: ConsentHub, filter: string) =>
90+
customDataSourceFilterPredicate(
91+
data,
92+
filter,
93+
this.displayedColumns,
94+
this.getDataForColumn,
95+
this
96+
);
97+
this.dataSource.sortData = (data: ConsentHub[], sort: MatSort) =>
98+
customDataSourceSort(data, sort, this.getDataForColumn, this);
99+
this.dataSource.sort = this.sort;
100+
this.dataSource.paginator = this.child.paginator;
101+
this.dataSource.filter = this.filterValue;
102+
}
103+
}
104+
105+
changeEnforceFlag(consentHub: ConsentHub, event: MatSlideToggleChange): void {
106+
// Prevent default slide toggle change
107+
event.source.checked = consentHub.enforceConsents;
108+
109+
const config = getDefaultDialogConfig();
110+
config.width = '550px';
111+
config.data = {
112+
theme: 'admin-theme',
113+
enforceConsents: consentHub.enforceConsents,
114+
consentHubName: consentHub.name,
115+
};
116+
117+
const dialogRef = this.dialog.open(EditEnforceConsentsDialogComponent, config);
118+
119+
dialogRef.afterClosed().subscribe((result: boolean) => {
120+
if (result) {
121+
consentHub.enforceConsents = !consentHub.enforceConsents;
122+
this.consentsManager.updateConsentHub({ consentHub: consentHub }).subscribe(
123+
(ch) => {
124+
event.source.checked = ch.enforceConsents;
125+
consentHub.enforceConsents = ch.enforceConsents;
126+
this.translate
127+
.get('SHARED.COMPONENTS.CONSENT_HUBS_LIST.CHANGE_ENFORCE_CONSENTS_SUCCESS')
128+
.subscribe((success) => {
129+
this.notificator.showSuccess(success);
130+
});
131+
},
132+
() => (consentHub.enforceConsents = !consentHub.enforceConsents)
133+
);
134+
}
135+
});
136+
}
137+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<div class="{{this.data.theme}}">
2+
<h1 mat-dialog-title>
3+
{{this.data.enforceConsents
4+
? ('DIALOGS.EDIT_ENFORCE_CONSENTS_FLAG.DISABLE_TITLE' | translate)
5+
: ('DIALOGS.EDIT_ENFORCE_CONSENTS_FLAG.ENFORCE_TITLE' | translate)}}
6+
</h1>
7+
<div mat-dialog-content>
8+
<p>
9+
{{this.data.enforceConsents
10+
? ('DIALOGS.EDIT_ENFORCE_CONSENTS_FLAG.DISABLE_DESCRIPTION' | translate)
11+
: ('DIALOGS.EDIT_ENFORCE_CONSENTS_FLAG.ENFORCE_DESCRIPTION' | translate)}}
12+
</p>
13+
14+
<mat-divider></mat-divider>
15+
<p class="mt-2 mb-2">{{this.data.consentHubName}}</p>
16+
<mat-divider></mat-divider>
17+
18+
<div class="font-weight-bold mt-3">
19+
{{this.data.enforceConsents
20+
? ('DIALOGS.EDIT_ENFORCE_CONSENTS_FLAG.DISABLE_BOLD_DESCRIPTION' | translate)
21+
: ('DIALOGS.EDIT_ENFORCE_CONSENTS_FLAG.ENFORCE_BOLD_DESCRIPTION' | translate)}}
22+
</div>
23+
</div>
24+
<div mat-dialog-actions>
25+
<button mat-flat-button class="ml-auto" (click)="onCancel()">
26+
{{'DIALOGS.EDIT_ENFORCE_CONSENTS_FLAG.CANCEL_BUTTON' | translate}}
27+
</button>
28+
<button mat-flat-button class="ml-2" color="accent" (click)="onSubmit()">
29+
{{this.data.enforceConsents
30+
? ('DIALOGS.EDIT_ENFORCE_CONSENTS_FLAG.DISABLE_BUTTON' | translate)
31+
: ('DIALOGS.EDIT_ENFORCE_CONSENTS_FLAG.ENFORCE_BUTTON' | translate)}}
32+
</button>
33+
</div>
34+
</div>

apps/admin-gui/src/app/shared/components/dialogs/edit-enforce-consents-dialog/edit-enforce-consents-dialog.component.scss

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Component, Inject } from '@angular/core';
2+
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
3+
4+
export interface EditEnforceConsentsDialogData {
5+
theme: string;
6+
enforceConsents: boolean;
7+
consentHubName: string;
8+
}
9+
10+
@Component({
11+
selector: 'app-perun-web-apps-edit-enforce-consents-dialog',
12+
templateUrl: './edit-enforce-consents-dialog.component.html',
13+
styleUrls: ['./edit-enforce-consents-dialog.component.scss'],
14+
})
15+
export class EditEnforceConsentsDialogComponent {
16+
constructor(
17+
public dialogRef: MatDialogRef<EditEnforceConsentsDialogComponent>,
18+
@Inject(MAT_DIALOG_DATA) public data: EditEnforceConsentsDialogData
19+
) {}
20+
21+
onCancel(): void {
22+
this.dialogRef.close(false);
23+
}
24+
25+
onSubmit(): void {
26+
this.dialogRef.close(true);
27+
}
28+
}

apps/admin-gui/src/app/shared/shared.module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ import { PerunNamespacePasswordFormModule } from '@perun-web-apps/perun/namespac
169169
import { AuditMessagesListComponent } from './components/audit-messages-list/audit-messages-list.component';
170170
import { AuditMessageDetailDialogComponent } from './components/dialogs/audit-message-detail-dialog/audit-message-detail-dialog.component';
171171
import { ParseEventNamePipe } from './pipes/parse-event-name.pipe';
172+
import { ConsentHubsListComponent } from './components/consent-hubs-list/consent-hubs-list.component';
173+
import { EditEnforceConsentsDialogComponent } from './components/dialogs/edit-enforce-consents-dialog/edit-enforce-consents-dialog.component';
172174

173175
@NgModule({
174176
imports: [
@@ -302,6 +304,7 @@ import { ParseEventNamePipe } from './pipes/parse-event-name.pipe';
302304
MemberOverviewMembershipComponent,
303305
MemberOverviewGroupsComponent,
304306
AuditMessagesListComponent,
307+
ConsentHubsListComponent,
305308
],
306309
declarations: [
307310
PerunNavComponent,
@@ -430,6 +433,8 @@ import { ParseEventNamePipe } from './pipes/parse-event-name.pipe';
430433
AuditMessagesListComponent,
431434
AuditMessageDetailDialogComponent,
432435
ParseEventNamePipe,
436+
ConsentHubsListComponent,
437+
EditEnforceConsentsDialogComponent,
433438
],
434439
providers: [AnyToStringPipe, ExtSourceTypePipe],
435440
})

0 commit comments

Comments
 (0)