Commit ffd3b732 authored by Steven Zou's avatar Steven Zou
Browse files

fix related issues

parent 79263a8b
...@@ -141,6 +141,18 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked { ...@@ -141,6 +141,18 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
this.account = Object.assign({}, this.session.getCurrentUser()); this.account = Object.assign({}, this.session.getCurrentUser());
this.formValueChanged = false; this.formValueChanged = false;
//Confirm inline alert is closed
this.inlineAlert.close();
//Clear check history
this.mailAlreadyChecked = {};
//Reset validation status
this.validationStateMap = {
"account_settings_email": true,
"account_settings_full_name": true
};
this.opened = true; this.opened = true;
} }
......
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true"> <clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true" [clrModalClosable]="false">
<h3 class="modal-title">{{'RESET_PWD.TITLE' | translate}}</h3> <h3 class="modal-title">{{'RESET_PWD.TITLE' | translate}}</h3>
<label class="modal-title reset-modal-title-override">{{'RESET_PWD.CAPTION' | translate}}</label> <label class="modal-title reset-modal-title-override">{{'RESET_PWD.CAPTION' | translate}}</label>
<inline-alert class="modal-title"></inline-alert> <inline-alert class="modal-title"></inline-alert>
......
...@@ -32,10 +32,15 @@ export class ForgotPasswordComponent { ...@@ -32,10 +32,15 @@ export class ForgotPasswordComponent {
} }
public open(): void { public open(): void {
this.opened = true; //Clear state data
this.validationState = true; this.validationState = true;
this.forceValid = true; this.forceValid = true;
this.onGoing = false;
this.email = "";
this.forgotPwdForm.resetForm(); this.forgotPwdForm.resetForm();
this.inlineAlert.close();
this.opened = true;
} }
public close(): void { public close(): void {
......
...@@ -93,9 +93,18 @@ export class PasswordSettingComponent implements AfterViewChecked { ...@@ -93,9 +93,18 @@ export class PasswordSettingComponent implements AfterViewChecked {
//Open modal dialog //Open modal dialog
open(): void { open(): void {
this.opened = true; //Reset state
this.pwdForm.reset();
this.formValueChanged = false; this.formValueChanged = false;
this.onCalling = false;
this.error = null;
this.validationStateMap = {
"newPassword": true,
"reNewPassword": true
};
this.pwdForm.reset();
this.inlineAlert.close();
this.opened = true;
} }
//Close the moal dialog //Close the moal dialog
......
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true"> <clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true" [clrModalClosable]="false">
<h3 class="modal-title">{{'RESET_PWD.TITLE' | translate}}</h3> <h3 class="modal-title">{{'RESET_PWD.TITLE' | translate}}</h3>
<label class="modal-title reset-modal-title-override">{{'RESET_PWD.CAPTION2' | translate}}</label> <label class="modal-title reset-modal-title-override">{{'RESET_PWD.CAPTION2' | translate}}</label>
<div class="modal-body" style="overflow-y: hidden;"> <div class="modal-body" style="overflow-y: hidden;">
......
...@@ -15,7 +15,10 @@ export class ResetPasswordComponent implements OnInit { ...@@ -15,7 +15,10 @@ export class ResetPasswordComponent implements OnInit {
opened: boolean = true; opened: boolean = true;
private onGoing: boolean = false; private onGoing: boolean = false;
private password: string = ""; private password: string = "";
private validationState: any = {}; private validationState: any = {
"newPassword": true,
"reNewPassword": true
};
private resetUuid: string = ""; private resetUuid: string = "";
private resetOk: boolean = false; private resetOk: boolean = false;
...@@ -49,8 +52,15 @@ export class ResetPasswordComponent implements OnInit { ...@@ -49,8 +52,15 @@ export class ResetPasswordComponent implements OnInit {
public open(): void { public open(): void {
this.resetOk = false; this.resetOk = false;
this.opened = true; this.onGoing = false;
this.validationState = {
"newPassword": true,
"reNewPassword": true
};
this.resetPwdForm.resetForm(); this.resetPwdForm.resetForm();
this.inlineAlert.close();
this.opened = true;
} }
public close(): void { public close(): void {
......
...@@ -153,8 +153,7 @@ export class SignInComponent implements AfterViewChecked, OnInit { ...@@ -153,8 +153,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
private handleUserCreation(user: User): void { private handleUserCreation(user: User): void {
if (user) { if (user) {
this.currentForm.setValue({ this.currentForm.setValue({
"login_username": user.username, "login_username": user.username
"login_password": user.password
}); });
} }
......
...@@ -59,8 +59,13 @@ export class SignUpComponent { ...@@ -59,8 +59,13 @@ export class SignUpComponent {
} }
open(): void { open(): void {
this.newUserForm.reset();//Reset form //Reset state
this.newUserForm.reset();
this.formValueChanged = false; this.formValueChanged = false;
this.error = null;
this.onGoing = false;
this.inlienAlert.close();
this.modal.open(); this.modal.open();
} }
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
</button> </button>
<div class="dropdown-menu"> <div class="dropdown-menu">
<a href="javascript:void(0)" clrDropdownItem (click)="openAccountSettingsModal()">{{'ACCOUNT_SETTINGS.PROFILE' | translate}}</a> <a href="javascript:void(0)" clrDropdownItem (click)="openAccountSettingsModal()">{{'ACCOUNT_SETTINGS.PROFILE' | translate}}</a>
<a href="javascript:void(0)" clrDropdownItem (click)="openChangePwdModal()">{{'ACCOUNT_SETTINGS.CHANGE_PWD' | translate}}</a> <a *ngIf="canChangePassword" href="javascript:void(0)" clrDropdownItem (click)="openChangePwdModal()">{{'ACCOUNT_SETTINGS.CHANGE_PWD' | translate}}</a>
<a *ngIf="canDownloadCert" href="/api/systeminfo/getcert" clrDropdownItem target="_blank">{{'ACCOUNT_SETTINGS.ROOT_CERT' | translate}}</a> <a *ngIf="canDownloadCert" href="/api/systeminfo/getcert" clrDropdownItem target="_blank">{{'ACCOUNT_SETTINGS.ROOT_CERT' | translate}}</a>
<a href="javascript:void(0)" clrDropdownItem (click)="openAboutDialog()">{{'ACCOUNT_SETTINGS.ABOUT' | translate}}</a> <a href="javascript:void(0)" clrDropdownItem (click)="openAboutDialog()">{{'ACCOUNT_SETTINGS.ABOUT' | translate}}</a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
......
...@@ -76,6 +76,12 @@ export class NavigatorComponent implements OnInit { ...@@ -76,6 +76,12 @@ export class NavigatorComponent implements OnInit {
this.appConfigService.getConfig().has_ca_root; this.appConfigService.getConfig().has_ca_root;
} }
public get canChangePassword(): boolean {
return this.session.getCurrentUser() &&
this.appConfigService.getConfig() &&
this.appConfigService.getConfig().auth_mode != 'ldap_auth';
}
matchLang(lang: string): boolean { matchLang(lang: string): boolean {
return lang.trim() === this.selectedLang; return lang.trim() === this.selectedLang;
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="form-group"> <div class="form-group">
<label for="authMode">{{'CONFIG.AUTH_MODE' | translate }}</label> <label for="authMode">{{'CONFIG.AUTH_MODE' | translate }}</label>
<div class="select"> <div class="select">
<select id="authMode" name="authMode" [disabled]="disabled(currentConfig.auth_mode)" [(ngModel)]="currentConfig.auth_mode.value"> <select id="authMode" name="authMode" (change)="handleOnChange($event)" [disabled]="disabled(currentConfig.auth_mode)" [(ngModel)]="currentConfig.auth_mode.value">
<option value="db_auth">{{'CONFIG.AUTH_MODE_DB' | translate }}</option> <option value="db_auth">{{'CONFIG.AUTH_MODE_DB' | translate }}</option>
<option value="ldap_auth">{{'CONFIG.AUTH_MODE_LDAP' | translate }}</option> <option value="ldap_auth">{{'CONFIG.AUTH_MODE_LDAP' | translate }}</option>
</select> </select>
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
<span class="tooltip-content">{{'CONFIG.TOOLTIP.PRO_CREATION_RESTRICTION' | translate}}</span> <span class="tooltip-content">{{'CONFIG.TOOLTIP.PRO_CREATION_RESTRICTION' | translate}}</span>
</a> </a>
</div> </div>
<div class="form-group"> <div class="form-group" *ngIf="showSelfReg">
<label for="selfReg">{{'CONFIG.SELF_REGISTRATION' | translate}}</label> <label for="selfReg">{{'CONFIG.SELF_REGISTRATION' | translate}}</label>
<clr-checkbox name="selfReg" id="selfReg" [(ngModel)]="currentConfig.self_registration.value" [disabled]="disabled(currentConfig.self_registration)"> <clr-checkbox name="selfReg" id="selfReg" [(ngModel)]="currentConfig.self_registration.value" [disabled]="disabled(currentConfig.self_registration)">
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top:-8px;"> <a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top:-8px;">
......
import { Component, Input, ViewChild } from '@angular/core'; import { Component, Input, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms'; import { NgForm } from '@angular/forms';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { Configuration } from '../config'; import { Configuration } from '../config';
...@@ -23,6 +23,14 @@ export class ConfigurationAuthComponent { ...@@ -23,6 +23,14 @@ export class ConfigurationAuthComponent {
this.currentConfig.auth_mode.value === 'ldap_auth'; this.currentConfig.auth_mode.value === 'ldap_auth';
} }
public get showSelfReg(): boolean {
if (!this.currentConfig || !this.currentConfig.auth_mode) {
return true;
} else {
return this.currentConfig.auth_mode.value != 'ldap_auth';
}
}
private disabled(prop: any): boolean { private disabled(prop: any): boolean {
return !(prop && prop.editable); return !(prop && prop.editable);
} }
...@@ -30,4 +38,15 @@ export class ConfigurationAuthComponent { ...@@ -30,4 +38,15 @@ export class ConfigurationAuthComponent {
public isValid(): boolean { public isValid(): boolean {
return this.authForm && this.authForm.valid; return this.authForm && this.authForm.valid;
} }
private handleOnChange($event): void {
if ($event && $event.target && $event.target["value"]) {
let authMode = $event.target["value"];
if (authMode === 'ldap_auth') {
if (this.currentConfig.self_registration.value) {
this.currentConfig.self_registration.value = false;//uncheck
}
}
}
}
} }
\ No newline at end of file
...@@ -280,14 +280,13 @@ export class ConfigurationComponent implements OnInit, OnDestroy { ...@@ -280,14 +280,13 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
} }
let allChanges = this.getChanges(); let allChanges = this.getChanges();
for (let prop in allChanges) { let ldapSearchPwd = allChanges["ldap_search_password"];
if (prop.startsWith("ldap_")) { if(ldapSearchPwd){
ldapSettings[prop] = allChanges[prop]; ldapSettings['ldap_search_password'] = ldapSearchPwd;
} }else{
delete ldapSettings['ldap_search_password'];
} }
console.info(ldapSettings);
this.testingOnGoing = true; this.testingOnGoing = true;
this.configService.testLDAPServer(ldapSettings) this.configService.testLDAPServer(ldapSettings)
.then(respone => { .then(respone => {
...@@ -300,7 +299,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy { ...@@ -300,7 +299,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
if(!err){ if(!err){
err = "UNKNOWN"; err = "UNKNOWN";
} }
this.msgHandler.showError("CONFIG.TEST_LDAP_FAILED", err); this.msgHandler.showError("CONFIG.TEST_LDAP_FAILED", {'param': err});
}); });
} }
......
...@@ -96,6 +96,7 @@ export class RecentLogComponent implements OnInit { ...@@ -96,6 +96,7 @@ export class RecentLogComponent implements OnInit {
let reg = new RegExp('.*' + terms + '.*', 'i'); let reg = new RegExp('.*' + terms + '.*', 'i');
return reg.test(log.username) || return reg.test(log.username) ||
reg.test(log.repo_name) || reg.test(log.repo_name) ||
reg.test(log.operation); reg.test(log.operation) ||
reg.test(log.repo_tag);
} }
} }
\ No newline at end of file
...@@ -21,7 +21,6 @@ import { SessionService } from '../../shared/session.service'; ...@@ -21,7 +21,6 @@ import { SessionService } from '../../shared/session.service';
export class NewUserFormComponent implements AfterViewChecked, OnInit { export class NewUserFormComponent implements AfterViewChecked, OnInit {
newUser: User = new User(); newUser: User = new User();
confirmedPwd: string = "";
@Input() isSelfRegistration: boolean = false; @Input() isSelfRegistration: boolean = false;
newUserFormRef: NgForm; newUserFormRef: NgForm;
...@@ -33,17 +32,10 @@ export class NewUserFormComponent implements AfterViewChecked, OnInit { ...@@ -33,17 +32,10 @@ export class NewUserFormComponent implements AfterViewChecked, OnInit {
constructor(private session: SessionService) { } constructor(private session: SessionService) { }
ngOnInit() { ngOnInit() {
this.formValueChanged = false; this.resetState();
} }
private validationStateMap: any = { private validationStateMap: any = {};
"username": true,
"email": true,
"realname": true,
"newPassword": true,
"confirmPassword": true,
"comment": true
};
private mailAlreadyChecked: any = {}; private mailAlreadyChecked: any = {};
private userNameAlreadyChecked: any = {}; private userNameAlreadyChecked: any = {};
...@@ -51,10 +43,27 @@ export class NewUserFormComponent implements AfterViewChecked, OnInit { ...@@ -51,10 +43,27 @@ export class NewUserFormComponent implements AfterViewChecked, OnInit {
private usernameTooltip: string = 'TOOLTIP.USER_NAME'; private usernameTooltip: string = 'TOOLTIP.USER_NAME';
private formValueChanged: boolean = false; private formValueChanged: boolean = false;
private checkOnGoing: any = { private checkOnGoing: any = {};
"username": false,
"email": false private resetState(): void {
}; this.mailAlreadyChecked = {};
this.userNameAlreadyChecked = {};
this.emailTooltip = 'TOOLTIP.EMAIL';
this.usernameTooltip = 'TOOLTIP.USER_NAME';
this.formValueChanged = false;
this.checkOnGoing = {
"username": false,
"email": false
};
this.validationStateMap = {
"username": true,
"email": true,
"realname": true,
"newPassword": true,
"confirmPassword": true,
"comment": true
};
}
public isChecking(key: string): boolean { public isChecking(key: string): boolean {
return !this.checkOnGoing[key]; return !this.checkOnGoing[key];
...@@ -161,7 +170,7 @@ export class NewUserFormComponent implements AfterViewChecked, OnInit { ...@@ -161,7 +170,7 @@ export class NewUserFormComponent implements AfterViewChecked, OnInit {
pwdEqualStatus = this.newUserForm.controls["confirmPassword"].value === this.newUserForm.controls["newPassword"].value; pwdEqualStatus = this.newUserForm.controls["confirmPassword"].value === this.newUserForm.controls["newPassword"].value;
} }
return this.newUserForm && return this.newUserForm &&
this.newUserForm.valid && this.newUserForm.valid &&
pwdEqualStatus && pwdEqualStatus &&
this.validationStateMap["username"] && this.validationStateMap["username"] &&
this.validationStateMap["email"];//Backend check should be valid as well this.validationStateMap["email"];//Backend check should be valid as well
...@@ -186,6 +195,7 @@ export class NewUserFormComponent implements AfterViewChecked, OnInit { ...@@ -186,6 +195,7 @@ export class NewUserFormComponent implements AfterViewChecked, OnInit {
//Reset form //Reset form
reset(): void { reset(): void {
this.resetState();
if (this.newUserForm) { if (this.newUserForm) {
this.newUserForm.reset(); this.newUserForm.reset();
} }
......
...@@ -11,6 +11,7 @@ import { CommonRoutes, AdmiralQueryParamKey } from '../../shared/shared.const'; ...@@ -11,6 +11,7 @@ import { CommonRoutes, AdmiralQueryParamKey } from '../../shared/shared.const';
import { AppConfigService } from '../../app-config.service'; import { AppConfigService } from '../../app-config.service';
import { maintainUrlQueryParmas } from '../../shared/shared.utils'; import { maintainUrlQueryParmas } from '../../shared/shared.utils';
import { MessageHandlerService } from '../message-handler/message-handler.service'; import { MessageHandlerService } from '../message-handler/message-handler.service';
import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
@Injectable() @Injectable()
export class AuthCheckGuard implements CanActivate, CanActivateChild { export class AuthCheckGuard implements CanActivate, CanActivateChild {
...@@ -18,7 +19,8 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild { ...@@ -18,7 +19,8 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
private authService: SessionService, private authService: SessionService,
private router: Router, private router: Router,
private appConfigService: AppConfigService, private appConfigService: AppConfigService,
private msgHandler: MessageHandlerService) { } private msgHandler: MessageHandlerService,
private searchTrigger: SearchTriggerService) { }
private isGuest(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { private isGuest(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const proRegExp = /\/harbor\/projects\/[\d]+\/.+/i; const proRegExp = /\/harbor\/projects\/[\d]+\/.+/i;
...@@ -33,6 +35,7 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild { ...@@ -33,6 +35,7 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
//When routing change, clear //When routing change, clear
this.msgHandler.clear(); this.msgHandler.clear();
this.searchTrigger.closeSearch(true);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
//Before activating, we firstly need to confirm whether the route is coming from peer part - admiral //Before activating, we firstly need to confirm whether the route is coming from peer part - admiral
......
...@@ -16,7 +16,7 @@ export class SignInGuard implements CanActivate, CanActivateChild { ...@@ -16,7 +16,7 @@ export class SignInGuard implements CanActivate, CanActivateChild {
//If user has logged in, should not login again //If user has logged in, should not login again
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let user = this.authService.getCurrentUser(); let user = this.authService.getCurrentUser();
if (!user) { if (user === null) {
this.authService.retrieveUser() this.authService.retrieveUser()
.then(() => { .then(() => {
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
......
...@@ -55,6 +55,10 @@ export class NewUserModalComponent { ...@@ -55,6 +55,10 @@ export class NewUserModalComponent {
open(): void { open(): void {
this.newUserForm.reset();//Reset form this.newUserForm.reset();//Reset form
this.formValueChanged = false; this.formValueChanged = false;
this.onGoing = false;
this.error = null;
this.inlineAlert.close();
this.opened = true; this.opened = true;
} }
......
<div class="row"> <div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<h2 class="custom-h2">{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}</h2> <h2 class="custom-h2">{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}</h2>
<div class="action-panel-pos"> <div class="action-panel-pos">
<span> <span>
<button *ngIf="canCreateUser" type="submit" class="btn btn-primary custom-add-button" (click)="addNewUser()"><clr-icon shape="add"></clr-icon> {{'USER.ADD_ACTION' | translate}}</button> <button *ngIf="canCreateUser" type="submit" class="btn btn-primary custom-add-button" (click)="addNewUser()"><clr-icon shape="add"></clr-icon> {{'USER.ADD_ACTION' | translate}}</button>
</span> </span>
<grid-filter class="filter-pos" filterPlaceholder='{{"USER.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter> <grid-filter class="filter-pos" filterPlaceholder='{{"USER.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
<span class="refresh-btn" (click)="refreshUser()"> <span class="refresh-btn" (click)="refreshUser()">
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon> <clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span> <span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
</span> </span>
</div> </div>
<div> <div>
<clr-datagrid> <clr-datagrid>
<clr-dg-column>{{'USER.COLUMN_NAME' | translate}}</clr-dg-column> <clr-dg-column>{{'USER.COLUMN_NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'USER.COLUMN_ADMIN' | translate}}</clr-dg-column> <clr-dg-column>{{'USER.COLUMN_ADMIN' | translate}}</clr-dg-column>
<clr-dg-column>{{'USER.COLUMN_EMAIL' | translate}}</clr-dg-column> <clr-dg-column>{{'USER.COLUMN_EMAIL' | translate}}</clr-dg-column>
<clr-dg-column>{{'USER.COLUMN_REG_NAME' | translate}}</clr-dg-column> <clr-dg-column>{{'USER.COLUMN_REG_NAME' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let user of users" [clrDgItem]="user"> <clr-dg-row *ngFor="let user of users" [clrDgItem]="user">
<clr-dg-action-overflow [hidden]="isMySelf(user.user_id)"> <clr-dg-action-overflow [hidden]="isMySelf(user.user_id)">
<button class="action-item" (click)="changeAdminRole(user)">{{adminActions(user)}}</button> <button class="action-item" (click)="changeAdminRole(user)">{{adminActions(user)}}</button>
<button class="action-item" (click)="deleteUser(user)">{{'USER.DEL_ACTION' | translate}}</button> <button class="action-item" (click)="deleteUser(user)">{{'USER.DEL_ACTION' | translate}}</button>
</clr-dg-action-overflow> </clr-dg-action-overflow>
<clr-dg-cell>{{user.username}}</clr-dg-cell> <clr-dg-cell>{{user.username}}</clr-dg-cell>
<clr-dg-cell>{{isSystemAdmin(user)}}</clr-dg-cell> <clr-dg-cell>{{isSystemAdmin(user)}}</clr-dg-cell>
<clr-dg-cell>{{user.email}}</clr-dg-cell> <clr-dg-cell>{{user.email}}</clr-dg-cell>
<clr-dg-cell> <clr-dg-cell>
{{user.creation_time}} {{user.creation_time}}
</clr-dg-cell> </clr-dg-cell>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer>{{users.length}} {{'USER.ADD_ACTION' | translate}}</clr-dg-footer> <clr-dg-footer>{{users.length}} {{'USER.ITEMS' | translate}}</clr-dg-footer>
</clr-datagrid> </clr-datagrid>
</div>
<new-user-modal (addNew)="addUserToList($event)"></new-user-modal>
</div>