Merge pull request #39917 from rhcs-dashboard/fix-43058-master

mgr/dashboard: warn password expiration in User Management
This commit is contained in:
Alfonso Martínez 2021-03-29 10:03:33 +02:00 committed by GitHub
commit f2b7fb057a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 139 additions and 6 deletions

View File

@ -20,3 +20,27 @@
{{ role }}{{ !isLast ? ", " : "" }}
</span>
</ng-template>
<ng-template #warningTpl
let-column="column"
let-value="value"
let-row="row">
<div [class.border-danger]="row.remainingDays < this.expirationDangerAlert"
[class.border-warning]="row.remainingDays < this.expirationWarningAlert && row.remainingDays >= this.expirationDangerAlert"
class="border-margin">
<div class="warning-content"> {{ value }} </div>
</div>
</ng-template>
<ng-template #durationTpl
let-column="column"
let-value="value"
let-row="row">
<i *ngIf="row.remainingDays < this.expirationWarningAlert"
i18n-title
title="User's password is about to expire"
[class.icon-danger-color]="row.remainingDays < this.expirationDangerAlert"
[class.icon-warning-color]="row.remainingDays < this.expirationWarningAlert && row.remainingDays >= this.expirationDangerAlert"
class="{{ icons.warning }}"></i>
<span title="{{ value | cdDate }}">{{ row.remainingTimeWithoutSeconds / 1000 | duration }}</span>
</ng-template>

View File

@ -0,0 +1,16 @@
@use './src/styles/vendor/variables' as vv;
.border-margin {
border-left: 3px solid transparent;
height: calc(100% + 10px);
margin-bottom: -5px;
margin-left: -5px;
margin-top: -5px;
}
.warning-content {
height: 100%;
padding-bottom: 5px;
padding-left: 5px;
padding-top: 5px;
}

View File

@ -79,4 +79,19 @@ describe('UserListComponent', () => {
}
});
});
it('should calculate remaining days', () => {
const day = 60 * 60 * 24 * 1000;
let today = Date.now();
expect(component.getRemainingDays(today + day * 2 + 1000)).toBe(2);
today = Date.now();
expect(component.getRemainingDays(today + day * 2 - 1000)).toBe(1);
today = Date.now();
expect(component.getRemainingDays(today + day + 1000)).toBe(1);
today = Date.now();
expect(component.getRemainingDays(today + 1)).toBe(0);
today = Date.now();
expect(component.getRemainingDays(today - (day + 1))).toBe(0);
expect(component.getRemainingDays(null)).toBe(undefined);
expect(component.getRemainingDays(undefined)).toBe(undefined);
});
});

View File

@ -2,6 +2,7 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { SettingsService } from '~/app/shared/api/settings.service';
import { UserService } from '~/app/shared/api/user.service';
import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
@ -12,7 +13,6 @@ import { CdTableAction } from '~/app/shared/models/cd-table-action';
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
import { Permission } from '~/app/shared/models/permissions';
import { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe';
import { EmptyPipe } from '~/app/shared/pipes/empty.pipe';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { ModalService } from '~/app/shared/services/modal.service';
@ -30,12 +30,19 @@ const BASE_URL = 'user-management/users';
export class UserListComponent implements OnInit {
@ViewChild('userRolesTpl', { static: true })
userRolesTpl: TemplateRef<any>;
@ViewChild('warningTpl', { static: true })
warningTpl: TemplateRef<any>;
@ViewChild('durationTpl', { static: true })
durationTpl: TemplateRef<any>;
permission: Permission;
tableActions: CdTableAction[];
columns: CdTableColumn[];
users: Array<any>;
expirationWarningAlert: number;
expirationDangerAlert: number;
selection = new CdTableSelection();
icons = Icons;
modalRef: NgbModalRef;
@ -46,7 +53,7 @@ export class UserListComponent implements OnInit {
private notificationService: NotificationService,
private authStorageService: AuthStorageService,
private urlBuilder: URLBuilderService,
private cdDatePipe: CdDatePipe,
private settingsService: SettingsService,
public actionLabels: ActionLabelsI18n
) {
this.permission = this.authStorageService.getPermissions().user;
@ -77,7 +84,8 @@ export class UserListComponent implements OnInit {
{
name: $localize`Username`,
prop: 'username',
flexGrow: 1
flexGrow: 1,
cellTemplate: this.warningTpl
},
{
name: $localize`Name`,
@ -104,19 +112,29 @@ export class UserListComponent implements OnInit {
cellTransformation: CellTemplate.checkIcon
},
{
name: $localize`Password expiration date`,
name: $localize`Password expires`,
prop: 'pwdExpirationDate',
flexGrow: 1,
pipe: this.cdDatePipe
cellTemplate: this.durationTpl
}
];
const settings: string[] = ['USER_PWD_EXPIRATION_WARNING_1', 'USER_PWD_EXPIRATION_WARNING_2'];
this.settingsService.getValues(settings).subscribe((data) => {
this.expirationWarningAlert = data['USER_PWD_EXPIRATION_WARNING_1'];
this.expirationDangerAlert = data['USER_PWD_EXPIRATION_WARNING_2'];
});
}
getUsers() {
this.userService.list().subscribe((users: Array<any>) => {
users.forEach((user) => {
user['remainingTimeWithoutSeconds'] = 0;
if (user['pwdExpirationDate'] && user['pwdExpirationDate'] > 0) {
user['pwdExpirationDate'] = user['pwdExpirationDate'] * 1000;
user['remainingTimeWithoutSeconds'] = this.getRemainingTimeWithoutSeconds(
user.pwdExpirationDate
);
user['remainingDays'] = this.getRemainingDays(user.pwdExpirationDate);
}
});
this.users = users;
@ -161,4 +179,48 @@ export class UserListComponent implements OnInit {
submitAction: () => this.deleteUser(username)
});
}
getWarningIconClass(expirationDays: number): any {
if (expirationDays === null || this.expirationWarningAlert > 10) {
return '';
}
const remainingDays = this.getRemainingDays(expirationDays);
if (remainingDays <= this.expirationDangerAlert) {
return 'icon-danger-color';
} else {
return 'icon-warning-color';
}
}
getWarningClass(expirationDays: number): any {
if (expirationDays === null || this.expirationWarningAlert > 10) {
return '';
}
const remainingDays = this.getRemainingDays(expirationDays);
if (remainingDays <= this.expirationDangerAlert) {
return 'border-danger';
} else {
return 'border-warning';
}
}
getRemainingDays(time: number): number {
if (time === undefined || time == null) {
return undefined;
}
if (time < 0) {
return 0;
}
const toDays = 1000 * 60 * 60 * 24;
return Math.max(0, Math.floor(this.getRemainingTime(time) / toDays));
}
getRemainingTimeWithoutSeconds(time: number): number {
const withSeconds = this.getRemainingTime(time);
return Math.floor(withSeconds / (1000 * 60)) * 60 * 1000;
}
getRemainingTime(time: number): number {
return time - Date.now();
}
}

View File

@ -8,7 +8,7 @@ describe('DurationPipe', () => {
});
it('transforms seconds into a human readable duration', () => {
expect(pipe.transform(0)).toBe('1 second');
expect(pipe.transform(0)).toBe('');
expect(pipe.transform(6)).toBe('6 seconds');
expect(pipe.transform(60)).toBe('1 minute');
expect(pipe.transform(600)).toBe('10 minutes');

View File

@ -13,6 +13,9 @@ export class DurationPipe implements PipeTransform {
* @return {string} The phrase describing the the amount of time
*/
transform(seconds: number): string {
if (seconds === null || seconds <= 0) {
return '';
}
const levels = [
[`${Math.floor(seconds / 31536000)}`, 'years'],
[`${Math.floor((seconds % 31536000) / 86400)}`, 'days'],

View File

@ -111,6 +111,7 @@ import { UpperFirstPipe } from './upper-first.pipe';
MillisecondsPipe,
NotAvailablePipe,
UpperFirstPipe,
DurationPipe,
MapPipe,
TruncatePipe
]

View File

@ -74,6 +74,18 @@ option {
white-space: pre;
}
.icon-danger-color {
color: vv.$danger;
}
.icon-warning-color {
color: vv.$warning;
}
.border-warning {
border-left: 4px solid vv.$warning;
}
.border-danger {
border-left: 4px solid vv.$danger;
}