Merge pull request #59703 from rhcs-dashboard/usermanagement-form

mgr/dashboard: carbonize user management form

Reviewed-by: Afreen Misbah <afreen@ibm.com>
This commit is contained in:
afreen23 2024-12-19 20:46:59 +05:30 committed by GitHub
commit 34dea65e0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 298 additions and 276 deletions

View File

@ -17,13 +17,48 @@ import { UserFormComponent } from './user-form/user-form.component';
import { UserListComponent } from './user-list/user-list.component';
import { UserPasswordFormComponent } from './user-password-form/user-password-form.component';
import { UserTabsComponent } from './user-tabs/user-tabs.component';
import { ButtonModule, GridModule, IconModule, InputModule } from 'carbon-components-angular';
import {
ButtonModule,
CheckboxModule,
DatePickerModule,
GridModule,
IconModule,
IconService,
InputModule,
ModalModule,
NumberModule,
RadioModule,
SelectModule,
UIShellModule,
TimePickerModule,
ComboBoxModule
} from 'carbon-components-angular';
// Icons
import ChevronDown from '@carbon/icons/es/chevron--down/16';
import Close from '@carbon/icons/es/close/32';
import AddFilled from '@carbon/icons/es/add--filled/32';
import SubtractFilled from '@carbon/icons/es/subtract--filled/32';
import Reset from '@carbon/icons/es/reset/32';
import EyeIcon from '@carbon/icons/es/view/16';
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
SharedModule,
UIShellModule,
InputModule,
GridModule,
ButtonModule,
IconModule,
CheckboxModule,
RadioModule,
SelectModule,
NumberModule,
ModalModule,
DatePickerModule,
TimePickerModule,
NgbNavModule,
NgbPopoverModule,
NgxPipeFunctionModule,
@ -31,8 +66,8 @@ import { ButtonModule, GridModule, IconModule, InputModule } from 'carbon-compon
NgbModule,
IconModule,
GridModule,
ButtonModule,
InputModule
InputModule,
ComboBoxModule
],
declarations: [
LoginComponent,
@ -46,7 +81,11 @@ import { ButtonModule, GridModule, IconModule, InputModule } from 'carbon-compon
UserPasswordFormComponent
]
})
export class AuthModule {}
export class AuthModule {
constructor(private iconService: IconService) {
this.iconService.registerAll([ChevronDown, Close, AddFilled, SubtractFilled, Reset, EyeIcon]);
}
}
const routes: Routes = [
{ path: '', redirectTo: 'users', pathMatch: 'full' },

View File

@ -4,11 +4,12 @@ export class UserFormRoleModel implements SelectOption {
name: string;
description: string;
selected = false;
scopes_permissions: object;
enabled = true;
constructor(name: string, description: string) {
scopes_permissions?: object;
enabled: boolean;
content: string;
constructor(name: string, description: string, content: string) {
this.name = name;
this.description = description;
this.content = content;
}
}

View File

@ -1,249 +1,205 @@
<div class="cd-col-form"
*cdFormLoading="loading">
<form name="userForm"
#formDir="ngForm"
[formGroup]="userForm"
novalidate>
<div class="card">
<div cdsCol
[columnNumbers]="{md: 4}">
<ng-container *cdFormLoading="loading">
<form #frm="ngForm"
#formDir="ngForm"
[formGroup]="userForm"
novalidate>
<div i18n="form title"
class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
<div class="card-body">
class="form-header">{{ action | titlecase }} {{ resource | upperFirst }}
</div>
<!-- UserName -->
<div class="form-item">
<cds-text-label labelInputID="username"
cdRequiredField="Username"
[invalid]="!userForm.controls.username.valid && userForm.controls.username.dirty"
[invalidText]="usernameError"
i18n>Username
<input cdsText
placeholder="Username..."
i18n-placeholder
id="username"
formControlName="username"
[invalid]="!userForm.controls.username.valid && userForm.controls.username.dirty"
autofocus
ngbTooltip="White spaces at the beginning and end will be trimmed"
i18n-ngbTooltip
cdTrim>
</cds-text-label>
<ng-template #usernameError>
<span *ngIf="userForm.showError('username', formDir, 'required')">
<ng-container i18n>
This field is required.
</ng-container>
</span>
<span *ngIf="userForm.showError('username', formDir, 'notUnique')">
<ng-container i18n>
The username already exists.
</ng-container>
</span>
</ng-template>
</div>
<!-- Password -->
<div class="form-item">
<cds-password-label labelInputID="password"
label="Password..."
[invalid]="!userForm.controls.password.valid && userForm.controls.password.dirty"
[invalidText]="passwordError"
i18n>Password
<cd-helper *ngIf="passwordPolicyHelpText.length > 0"
class="text-pre-wrap"
html="{{ passwordPolicyHelpText }}">
</cd-helper>
<input cdsPassword
type="password"
placeholder="Password..."
id="password"
autocomplete="new-password"
formControlName="password"
>
</cds-password-label>
<ng-template #passwordError>
<span class="invalid-feedback"
*ngIf="userForm.showError('password', formDir, 'match')"
i18n>Password confirmation doesn't match the password.
</span>
<span class="invalid-feedback"
*ngIf="userForm.showError('password', formDir, 'required')"
i18n>This field is required.</span>
<span class="invalid-feedback"
*ngIf="userForm.showError('password', formDir, 'passwordPolicy')">
{{ passwordValuation }}
</span>
</ng-template>
</div>
<!-- Username -->
<div class="form-group row">
<label class="cd-col-form-label"
[ngClass]="{'required': mode !== userFormMode.editing}"
for="username"
i18n>Username</label>
<div class="cd-col-form-input">
<input class="form-control"
type="text"
placeholder="Username..."
id="username"
name="username"
formControlName="username"
autocomplete="off"
autofocus
ngbTooltip="White spaces at the beginning and end will be trimmed"
i18n-ngbTooltip
cdTrim>
<span class="invalid-feedback"
*ngIf="userForm.showError('username', formDir, 'required')"
i18n>This field is required.</span>
<span class="invalid-feedback"
*ngIf="userForm.showError('username', formDir, 'notUnique')"
i18n>The username already exists.</span>
</div>
</div>
<!-- Password -->
<div class="form-group row"
*ngIf="!authStorageService.isSSO()">
<label class="cd-col-form-label"
for="password">
<ng-container i18n>Password</ng-container>
<cd-helper *ngIf="passwordPolicyHelpText.length > 0"
class="text-pre-wrap"
html="{{ passwordPolicyHelpText }}">
</cd-helper>
</label>
<div class="cd-col-form-input">
<div class="input-group">
<input class="form-control"
type="password"
placeholder="Password..."
id="password"
name="password"
autocomplete="new-password"
formControlName="password">
<button type="button"
class="btn btn-light"
cdPasswordButton="password">
</button>
</div>
<div class="password-strength-level">
<div class="{{ passwordStrengthLevelClass }}"
data-toggle="tooltip"
title="{{ passwordValuation }}">
</div>
</div>
<span class="invalid-feedback"
*ngIf="userForm.showError('password', formDir, 'required')"
i18n>This field is required.</span>
<span class="invalid-feedback"
*ngIf="userForm.showError('password', formDir, 'passwordPolicy')">
{{ passwordValuation }}
</span>
</div>
</div>
<!-- Confirm password -->
<div class="form-group row"
*ngIf="!authStorageService.isSSO()">
<label i18n
class="cd-col-form-label"
for="confirmpassword">Confirm password</label>
<div class="cd-col-form-input">
<div class="input-group">
<input class="form-control"
type="password"
placeholder="Confirm password..."
id="confirmpassword"
name="confirmpassword"
autocomplete="new-password"
formControlName="confirmpassword">
<button type="button"
class="btn btn-light"
cdPasswordButton="confirmpassword">
</button>
<span class="invalid-feedback"
*ngIf="userForm.showError('confirmpassword', formDir, 'match')"
i18n>Password confirmation doesn't match the password.</span>
</div>
<span class="invalid-feedback"
*ngIf="userForm.showError('confirmpassword', formDir, 'required')"
i18n>This field is required.</span>
</div>
</div>
<!-- Password expiration date -->
<div class="form-group row"
*ngIf="!authStorageService.isSSO()">
<label class="cd-col-form-label"
[ngClass]="{'required': pwdExpirationSettings.pwdExpirationSpan > 0}"
for="pwdExpirationDate">
<ng-container i18n>Password expiration date</ng-container>
<cd-helper class="text-pre-wrap"
*ngIf="pwdExpirationSettings.pwdExpirationSpan == 0">
<p>
<!-- Confirm password -->
<div class="form-item">
<cds-password-label labelInputID="confirmpassword"
label="Confirm password..."
[invalid]="!userForm.controls.confirmpassword.valid && userForm.controls.confirmpassword.dirty"
[invalidText]="confirmpasswordError"
i18n> Confirm password
<input cdsPassword
type="password"
placeholder="Confirm password..."
id="confirmpassword"
formControlName="confirmpassword">
</cds-password-label>
<ng-template #confirmpasswordError>
<span class="invalid-feedback"
*ngIf="userForm.showError('confirmpassword', formDir, 'match')"
i18n>Password confirmation doesn't match the password.</span>
<span class="invalid-feedback"
*ngIf="userForm.showError('confirmpassword', formDir, 'required')"
i18n>This field is required.</span>
</ng-template>
</div>
<!-- Password expiration date -->
<div class="form-item"
*ngIf="!authStorageService.isSSO()">
<cds-text-label [ngClass]="{'required': pwdExpirationSettings.pwdExpirationSpan > 0}">{{'Password Expiration Date'}}
<cd-helper class="text-pre-wrap"
*ngIf="pwdExpirationSettings.pwdExpirationSpan == 0">
<span>
The Dashboard setting defining the expiration interval of
passwords is currently set to <strong>0</strong>. This means
if a date is set, the user password will only expire once.
</p>
<p>
Consider configuring the Dashboard setting
<a routerLink="/mgr-modules/edit/dashboard"
class="alert-link">USER_PWD_EXPIRATION_SPAN</a>
in order to let passwords expire periodically.
</p>
</cd-helper>
</label>
<div class="cd-col-form-input">
<div class="input-group">
<input class="form-control"
i18n-placeholder
placeholder="Password expiration date..."
id="pwdExpirationDate"
name="pwdExpirationDate"
formControlName="pwdExpirationDate"
[ngbPopover]="popContent"
triggers="manual"
#p="ngbPopover"
(click)="p.open()"
(keypress)="p.close()">
<button type="button"
class="btn btn-light"
(click)="clearExpirationDate()">
<i class="icon-prepend {{ icons.destroy }}"></i>
</button>
<span class="invalid-feedback"
*ngIf="userForm.showError('pwdExpirationDate', formDir, 'required')"
i18n>This field is required.</span>
</div>
</div>
</div>
<!-- Name -->
<div class="form-group row">
<label i18n
class="cd-col-form-label"
for="name">Full name</label>
<div class="cd-col-form-input">
<input class="form-control"
type="text"
placeholder="Full name..."
id="name"
name="name"
formControlName="name">
</div>
</div>
<!-- Email -->
<div class="form-group row">
<label i18n
class="cd-col-form-label"
for="email">Email</label>
<div class="cd-col-form-input">
<input class="form-control"
type="email"
placeholder="Email..."
id="email"
name="email"
formControlName="email">
<span class="invalid-feedback"
*ngIf="userForm.showError('email', formDir, 'email')"
i18n>Invalid email.</span>
</div>
</div>
<!-- Roles -->
<div class="form-group row">
<label class="cd-col-form-label"
i18n>Roles</label>
<div class="cd-col-form-input">
<span class="no-border full-height"
*ngIf="allRoles">
<cd-select-badges [data]="userForm.controls.roles.value"
[options]="allRoles"
[messages]="messages"></cd-select-badges>
</span>
</div>
</div>
<!-- Enabled -->
<div class="form-group row"
*ngIf="!isCurrentUser()">
<div class="cd-col-form-offset">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
id="enabled"
name="enabled"
formControlName="enabled">
<label class="custom-control-label"
for="enabled"
i18n>Enabled</label>
</div>
</div>
</div>
<!-- Force change password -->
<div class="form-group row"
*ngIf="!isCurrentUser() && !authStorageService.isSSO()">
<div class="cd-col-form-offset">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
id="pwdUpdateRequired"
name="pwdUpdateRequired"
formControlName="pwdUpdateRequired">
<label class="custom-control-label"
for="pwdUpdateRequired"
i18n>User must change password at next logon</label>
</div>
</div>
</div>
<span>Consider configuring the Dashboard setting
<a routerLink="/mgr-modules/edit/dashboard"
class="alert-link">USER_PWD_EXPIRATION_SPAN</a>
in order to let passwords expire periodically.
</span>
</cd-helper>
<cd-date-time-picker [control]="userForm.get('pwdExpirationDate')"
placeHolder="Password expiration date"
[hasTime]="false"
[defaultDate]="passwordexp"
i18n-name
>
</cd-date-time-picker>
</cds-text-label>
<span class="invalid-feedback"
*ngIf="userForm.showError('pwdExpirationDate', formDir, 'required')"
i18n>This field is required.
</span>
</div>
<div class="card-footer">
<cd-form-button-panel (submitActionEvent)="submit()"
[form]="userForm"
[submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
wrappingClass="text-right"></cd-form-button-panel>
<!--Full Name-->
<div class="form-item">
<cds-text-label for="name"
i18n> Full Name
<input cdsText
type="text"
placeholder="Full Name..."
id="name"
formControlName="name">
</cds-text-label>
</div>
</div>
</form>
<!-- Email -->
<div class="form-item">
<cds-text-label for="email"
[invalid]="!userForm.controls.email.valid && userForm.controls.email.dirty"
[invalidText]="emailError"
i18n>
Email
<input cdsText
type="email"
placeholder="Email..."
id="email"
formControlName="email">
</cds-text-label>
<ng-template #emailError>
<span class="invalid-feedback"
*ngIf="userForm.showError('email', formDir, 'email')"
i18n>Invalid email.
</span>
</ng-template>
</div>
<!-- Roles -->
<div class="form-item"
*ngIf="allRoles">
<cds-combo-box label="Roles"
type="multi"
selectionFeedback="top-after-reopen"
for="roles"
formControlName="roles"
id="roles"
placeholder="Select Roles..."
i18n-placeholder
[appendInline]="true"
[items]="allRoles"
itemValueKey="name"
i18n>
<cds-dropdown-list></cds-dropdown-list>
</cds-combo-box>
</div>
<!-- Enabled -->
<div class="form-item"
*ngIf="!isCurrentUser()">
<cds-checkbox id="enabled"
formControlName="enabled"
name="enabled"
i18n>Enabled
</cds-checkbox>
</div>
<!-- Force change password -->
<div class="form-item"
*ngIf="!isCurrentUser() && !authStorageService.isSSO()">
<cds-checkbox id="pwdUpdateRequired"
formControlName="pwdUpdateRequired"
name="pwdUpdateRequired"
i18n>User must change password at next logon
</cds-checkbox>
</div>
<!--Submit Button-->
<cd-form-button-panel (submitActionEvent)="submit()"
[form]="userForm"
[submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
wrappingClass="text-right">
</cd-form-button-panel>
</form>
</ng-container>
</div>
<ng-template #removeSelfUserReadUpdatePermissionTpl>

View File

@ -55,7 +55,8 @@ export class UserFormComponent extends CdForm implements OnInit {
icons = Icons;
pwdExpirationSettings: CdPwdExpirationSettings;
pwdExpirationFormat = 'YYYY-MM-DD';
selectedRole: string[];
passwordexp: boolean = false;
constructor(
private authService: AuthService,
private authStorageService: AuthStorageService,
@ -91,6 +92,7 @@ export class UserFormComponent extends CdForm implements OnInit {
password: [
'',
[],
[
CdValidators.passwordPolicy(
this.userService,
@ -105,7 +107,7 @@ export class UserFormComponent extends CdForm implements OnInit {
]
],
confirmpassword: [''],
pwdExpirationDate: [undefined],
pwdExpirationDate: [''],
email: ['', [CdValidators.email]],
roles: [[]],
enabled: [true, [Validators.required]],
@ -121,8 +123,10 @@ export class UserFormComponent extends CdForm implements OnInit {
if (this.router.url.startsWith('/user-management/users/edit')) {
this.mode = this.userFormMode.editing;
this.action = this.actionLabels.EDIT;
this.passwordexp = false;
} else {
this.action = this.actionLabels.CREATE;
this.passwordexp = true;
}
const observables = [this.roleService.list(), this.settingsService.getStandardSettings()];
@ -130,6 +134,7 @@ export class UserFormComponent extends CdForm implements OnInit {
(result: [UserFormRoleModel[], CdPwdExpirationSettings]) => {
this.allRoles = _.map(result[0], (role) => {
role.enabled = true;
role.content = `${role.name}, ${role.description}`;
return role;
});
this.pwdExpirationSettings = new CdPwdExpirationSettings(result[1]);
@ -158,7 +163,6 @@ export class UserFormComponent extends CdForm implements OnInit {
this.userService.get(username).subscribe((userFormModel: UserFormModel) => {
this.response = _.cloneDeep(userFormModel);
this.setResponse(userFormModel);
this.loadingReady();
});
});
@ -173,20 +177,28 @@ export class UserFormComponent extends CdForm implements OnInit {
this.userForm.get(key).setValue(response[key])
);
const expirationDate = response['pwdExpirationDate'];
if (expirationDate) {
this.passwordexp = false;
this.userForm
.get('pwdExpirationDate')
.setValue(moment(expirationDate * 1000).format(this.pwdExpirationFormat));
} else {
this.passwordexp = true;
}
}
getRequest(): UserFormModel {
const userFormModel = new UserFormModel();
['username', 'password', 'name', 'email', 'roles', 'enabled', 'pwdUpdateRequired'].forEach(
(key) => (userFormModel[key] = this.userForm.get(key).value)
(key) => {
userFormModel[key] = this.userForm.get(key).value;
}
);
const expirationDate = this.userForm.get('pwdExpirationDate').value;
if (expirationDate) {
this.passwordexp = false;
const mom = moment(expirationDate, this.pwdExpirationFormat);
if (
this.mode !== this.userFormMode.editing ||

View File

@ -1,22 +1,23 @@
<div cdsCol
class="form-item">
<div cdsRow>
<cds-date-picker [label]="name"
i18n-label
placeholder="NOT PROTECTED"
formControlname="expiresAt"
dateFormat="Y/m/d"
[value]="date"
(valueChange)="onModelChange($event)"
[helperText]="helperText"
[disabled]="disabled"
cdsTheme="theme"></cds-date-picker>
<cds-timepicker (valueChange)="onModelChange($event)"
[(ngModel)]="time"
label="Select a time"
[disabled]="disabled"
pattern="(1[012]|[0-9]):[0-5][0-9]"
*ngIf="hasTime">
<div cdsCol>
<div cdsRow
class="form-item-append">
<cds-text-label>{{name}}
<cds-date-picker i18n-label
[placeholder]="placeHolder"
formControlname="expiresAt"
dateFormat="Y/m/d"
[value]="date"
(valueChange)="onModelChange($event)"
[helperText]="helperText"
[disabled]="disabled"
cdsTheme="theme"></cds-date-picker>
</cds-text-label>
<cds-text-label *ngIf="hasTime">Select a time
<cds-timepicker (valueChange)="onModelChange($event)"
[(ngModel)]="time"
[disabled]="disabled"
pattern="(1[012]|[0-9]):[0-5][0-9]"
*ngIf="hasTime">
<cds-timepicker-select [(ngModel)]="ampm"
[disabled]="disabled"
(valueChange)="onModelChange($event)">
@ -24,5 +25,7 @@
value="AM">AM</option>
<option value="PM">PM</option>
</cds-timepicker-select>
</cds-timepicker></div>
</cds-timepicker>
</cds-text-label>
</div>
</div>

View File

@ -25,7 +25,8 @@ export class DateTimePickerComponent implements OnInit {
@Input()
helperText = '';
@Input()
placeHolder = '';
@Input()
disabled = false;
@ -39,9 +40,8 @@ export class DateTimePickerComponent implements OnInit {
date: { [key: number]: string }[] = [];
time: string;
ampm: string;
sub: Subscription;
@Input() defaultDate: boolean = false;
constructor(private calendar: NgbCalendar) {}
ngOnInit() {
@ -59,8 +59,12 @@ export class DateTimePickerComponent implements OnInit {
if (!mom.isValid() || mom.isBefore(moment())) {
mom = moment();
}
if (this.defaultDate) {
this.date.push([]);
} else {
this.date.push(mom.format('YYYY-MM-DD'));
}
this.date.push(mom.format('YYYY-MM-DD'));
const time = mom.format('HH:mm:ss');
this.time = mom.format('hh:mm');
this.ampm = mom.hour() >= 12 ? 'PM' : 'AM';
@ -76,7 +80,9 @@ export class DateTimePickerComponent implements OnInit {
onModelChange(event?: any) {
if (event) {
if (Array.isArray(event)) {
if (event.length === 0) {
this.datetime.date = { date: null, time: null, ampm: null };
} else if (Array.isArray(event)) {
this.datetime.date = moment(event[0]).format('YYYY-MM-DD');
} else if (event && ['AM', 'PM'].includes(event)) {
const initialMoment = moment(this.datetime.time, 'hh:mm:ss A');

View File

@ -5,7 +5,8 @@
<ng-content></ng-content>
</ng-template>
<cds-tooltip [description]="popoverTpl">
<cds-tooltip [description]="popoverTpl"
[autoAlign]="true">
<svg cdsIcon="information"
size="16"
title="info"></svg>

View File

@ -33,6 +33,7 @@ $content-theme: map-merge(
text-primary: vv.$dark,
text-secondary: vv.$dark,
text-disabled: vv.$gray-500,
icon-secondary: vv.$gray-800,
field-01: colors.$gray-10,
interactive: vv.$primary
)