]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: warn user's password expiration in user management
authorPere Diaz Bou <pere-altea@hotmail.com>
Thu, 25 Mar 2021 17:40:40 +0000 (18:40 +0100)
committerPere Diaz Bou <pere-altea@hotmail.com>
Thu, 25 Mar 2021 17:40:40 +0000 (18:40 +0100)
Warn about user's incoming password expiration on the User Management with an icon.
Fixes: https://tracker.ceph.com/issues/43058
Signed-off-by: Pere Diaz Bou <pere-altea@hotmail.com>
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.html [changed mode: 0644->0755]
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.scss [changed mode: 0644->0755]
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.ts [changed mode: 0644->0755]
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/duration.pipe.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/duration.pipe.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts
src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_basics.scss

old mode 100644 (file)
new mode 100755 (executable)
index 89ed21b..5676f3f
     {{ 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>
old mode 100644 (file)
new mode 100755 (executable)
index e69de29..4aada41
@@ -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;
+}
index a1b9cfd14e1b059ff98116d99a1a716099c8f4f5..01e68e6d9296d10a1bd40441fa19ddc224951bde 100644 (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);
+  });
 });
old mode 100644 (file)
new mode 100755 (executable)
index 09c0d82..3a16fdc
@@ -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();
+  }
 }
index 1b0e22578aafee7f4c0812e49f763897456f7e60..d939e2e58594926119a4ed6f980b5374a3f68696 100644 (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');
index 4675fc0f6c6d6fbe5c85af7b513c160d8353139f..687153b21b6e7e24d9398eee2a6d820564a23d97 100644 (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'],
index 3deb535929d3c5509bdb15039e2ac22174f60dba..5b94f2d7d9de0817f79d3ea0f44c3dc264b0bf1d 100755 (executable)
@@ -111,6 +111,7 @@ import { UpperFirstPipe } from './upper-first.pipe';
     MillisecondsPipe,
     NotAvailablePipe,
     UpperFirstPipe,
+    DurationPipe,
     MapPipe,
     TruncatePipe
   ]
index 04f365e6daacd253073bda62c1a687573e6eb4b6..29cd040e8728bb26e07ba8ff2450d00aef5c8896 100644 (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;
 }