fix(console): text color shades, ui fixes, state handle restore (#3698)

* common project grant dialog, info box, label policy

* text styles, statehandler fix

* dialog, btn alignment, i18n

* search-user theme colors

* filter formfield sizing

* redirect uris

* shortcut layout

* settings grid type rest, shortcuts linking

* login policy, reset button consistency, metadata

* permission checks
This commit is contained in:
Max Peintner
2022-05-25 09:33:18 +02:00
committed by GitHub
parent 09b021b257
commit b6deed3e34
90 changed files with 1055 additions and 883 deletions
@@ -17,7 +17,7 @@
<div class="max-width-container">
<cnsl-meta-layout>
<cnsl-sidenav [(ngModel)]="currentSetting" [settingsList]="settingsList">
<cnsl-sidenav [(ngModel)]="currentSetting" [settingsList]="settingsList" queryParam="id">
<ng-container *ngIf="currentSetting === 'general'">
<cnsl-card
*ngIf="user && user.human && user.human.profile"
@@ -28,7 +28,7 @@
opacity: 0.6;
font-size: 15px;
cursor: pointer;
color: map-get($foreground, base);
color: map-get($foreground, text);
display: flex;
align-items: center;
@@ -2,8 +2,9 @@ import { MediaMatcher } from '@angular/cdk/layout';
import { Location } from '@angular/common';
import { Component, EventEmitter, OnDestroy } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import { Subscription, take } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
@@ -57,7 +58,15 @@ export class AuthUserDetailComponent implements OnDestroy {
private breadcrumbService: BreadcrumbService,
private mediaMatcher: MediaMatcher,
private _location: Location,
activatedRoute: ActivatedRoute,
) {
activatedRoute.queryParams.pipe(take(1)).subscribe((params: Params) => {
const { id } = params;
if (id) {
this.currentSetting = id;
}
});
const mediaq: string = '(max-width: 500px)';
const small = this.mediaMatcher.matchMedia(mediaq).matches;
if (small) {
@@ -1,42 +1,55 @@
<div mat-dialog-title class="title-row">
<h1 class="title">{{'USER.METADATA.TITLE' | translate}}</h1>
<div class="title-row">
<h1 class="metadata-title">{{ 'USER.METADATA.TITLE' | translate }}</h1>
<span class="fill-space"></span>
<p *ngIf="ts" class="ts cnsl-secondary-text">{{ts | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</p>
<p *ngIf="ts" class="ts cnsl-secondary-text">{{ ts | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</p>
<mat-spinner *ngIf="loading" diameter="20"></mat-spinner>
<button class="icon-button" mat-icon-button (click)="load()">
<mat-icon class="icon">refresh</mat-icon>
</button>
</div>
<p class="desc">{{'USER.METADATA.DESCRIPTION' | translate}}</p>
<div mat-dialog-content>
<p class="desc">{{ 'USER.METADATA.DESCRIPTION' | translate }}</p>
<div mat-dialog-content class="metadata-dialog-content">
<form *ngFor="let md of metadata; index as i" (ngSubmit)="saveElement(i)">
<div class="content">
<cnsl-form-field #key id="key{{i}}" class="formfield">
<cnsl-form-field #key id="key{{ i }}" class="formfield">
<cnsl-label>{{ 'USER.METADATA.KEY' | translate }}</cnsl-label>
<input cnslInput [(ngModel)]="md.key" [ngModelOptions]="{standalone: true}" />
<input cnslInput [(ngModel)]="md.key" [ngModelOptions]="{ standalone: true }" />
</cnsl-form-field>
<cnsl-form-field #value id="value{{i}}" class="formfield">
<cnsl-form-field #value id="value{{ i }}" class="formfield">
<cnsl-label>{{ 'USER.METADATA.VALUE' | translate }}</cnsl-label>
<input cnslInput [(ngModel)]="md.value" [ngModelOptions]="{standalone: true}" />
<input cnslInput [(ngModel)]="md.value" [ngModelOptions]="{ standalone: true }" />
</cnsl-form-field>
<button mat-icon-button [disabled]="!(md.key && md.value)" class="set-button" type="submit" color="primary"
matTooltip="{{ 'ACTIONS.SAVE' | translate }}">
<button
mat-icon-button
[disabled]="!(md.key && md.value)"
class="set-button"
type="submit"
color="primary"
matTooltip="{{ 'ACTIONS.SAVE' | translate }}"
>
<i class="las la-save"></i>
</button>
<button mat-icon-button (click)="removeEntry(i)" [disabled]="metadata.length < 2 && i === 0 && !md.key"
class="rm-button" type="button" color="warn" matTooltip="{{ 'ACTIONS.REMOVE' | translate }}">
<button
mat-icon-button
(click)="removeEntry(i)"
[disabled]="metadata.length < 2 && i === 0 && !md.key"
class="rm-button"
type="button"
color="warn"
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
>
<i class="las la-trash"></i>
</button>
</div>
</form>
<button color="primary" (click)="addEntry()" mat-stroked-button color="primary" class="continue-button" type="button">
<button (click)="addEntry()" mat-stroked-button class="continue-button" type="button">
<mat-icon>add</mat-icon>
{{ 'ACTIONS.ADD' | translate }}
</button>
</div>
<div mat-dialog-actions class="action">
<button cdkFocusInitial color="primary" mat-stroked-button class="ok-button" (click)="closeDialog()">
{{'ACTIONS.CLOSE' | translate}}
<button cdkFocusInitial mat-stroked-button class="ok-button" (click)="closeDialog()">
{{ 'ACTIONS.CLOSE' | translate }}
</button>
</div>
</div>
@@ -2,8 +2,8 @@
display: flex;
align-items: center;
.title {
font-size: 1.5rem;
.metadata-title {
font-size: 1.3rem;
margin: 0;
}
@@ -23,23 +23,27 @@
}
}
.content {
display: flex;
align-items: center;
margin: 0 -0.5rem;
.metadata-dialog-content {
min-width: 400px;
.formfield {
flex: 1;
margin: 0 0.5rem;
.content {
display: flex;
align-items: center;
margin: 0 -0.5rem;
@media only screen and (max-width: 450px) {
flex-basis: 100%;
.formfield {
flex: 1;
margin: 0 0.5rem;
@media only screen and (max-width: 450px) {
flex-basis: 100%;
}
}
}
.rm-button,
.set-button {
margin-top: 14px;
.rm-button,
.set-button {
margin-top: 14px;
}
}
}
@@ -1,22 +1,17 @@
<cnsl-card class="metadata-details" title="{{ 'USER.METADATA.TITLE' | translate }}">
<div card-actions class="actions">
<div class="metadata-actions">
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<button mat-raised-button color="primary" class="edit" (click)="editMetadata()">{{'ACTIONS.EDIT' |
translate}}</button>
<button matTooltip="{{'ACTIONS.REFRESH' | translate}}" class="refresh-btn" (click)="loadMetadata()" mat-icon-button
aria-label="refresh contributors">
<mat-icon class="icon">refresh</mat-icon>
</button>
<button mat-raised-button color="primary" class="edit" (click)="editMetadata()">{{ 'ACTIONS.EDIT' | translate }}</button>
</div>
<ng-container *ngIf="metadata?.length; else emptyList">
<div class="metadata-set" *ngFor="let md of metadata">
<span class="first cnsl-secondary-text">{{md.key}}</span>
<span class="first cnsl-secondary-text">{{ md.key }}</span>
<span class="second">{{ md.value }}</span>
</div>
</ng-container>
<ng-template #emptyList>
<p class="empty-desc cnsl-secondary-text">{{'USER.METADATA.EMPTY' | translate}}</p>
<p class="empty-desc cnsl-secondary-text">{{ 'USER.METADATA.EMPTY' | translate }}</p>
</ng-template>
</cnsl-card>
</cnsl-card>
@@ -1,9 +1,10 @@
.metadata-details {
padding-bottom: 1rem;
.actions {
.metadata-actions {
display: flex;
align-items: center;
justify-content: flex-end;
.edit {
font-size: 14px;
@@ -53,6 +54,7 @@
.empty-desc {
margin: 0;
font-size: 14px;
margin-bottom: 0.5rem;
}
.refresh-btn {
@@ -16,8 +16,7 @@ export class MetadataComponent implements OnInit {
public metadata: Metadata.AsObject[] = [];
public loading: boolean = false;
constructor(private dialog: MatDialog, private service: ManagementService, private toast: ToastService,
) { }
constructor(private dialog: MatDialog, private service: ManagementService, private toast: ToastService) {}
ngOnInit(): void {
this.loadMetadata();
@@ -37,17 +36,20 @@ export class MetadataComponent implements OnInit {
public loadMetadata(): Promise<any> {
this.loading = true;
return (this.service as ManagementService).listUserMetadata(this.userId).then(resp => {
this.loading = false;
this.metadata = resp.resultList.map(md => {
return {
key: md.key,
value: atob(md.value as string),
};
return (this.service as ManagementService)
.listUserMetadata(this.userId)
.then((resp) => {
this.loading = false;
this.metadata = resp.resultList.map((md) => {
return {
key: md.key,
value: atob(md.value as string),
};
});
})
.catch((error) => {
this.loading = false;
this.toast.showError(error);
});
}).catch((error) => {
this.loading = false;
this.toast.showError(error);
});
}
}
@@ -37,7 +37,9 @@
</cnsl-top-view>
<div *ngIf="loading" class="max-width-container">
<mat-progress-spinner diameter="25" color="primary" mode="indeterminate"></mat-progress-spinner>
<div class="sp-wrapper">
<mat-progress-spinner diameter="25" color="primary" mode="indeterminate"></mat-progress-spinner>
</div>
</div>
<div *ngIf="!loading && !user" class="max-width-container">
@@ -46,7 +48,7 @@
<div class="max-width-container" *ngIf="user && (['user.write$', 'user.write:' + user.id] | hasRole) as canWrite$">
<cnsl-meta-layout>
<cnsl-sidenav [(ngModel)]="currentSetting" [settingsList]="settingsList">
<cnsl-sidenav [(ngModel)]="currentSetting" [settingsList]="settingsList" queryParam="id">
<div *ngIf="error" class="max-width-container">
<p>{{ error }}</p>
</div>
@@ -42,3 +42,7 @@
.resendemail {
margin-top: 0.5rem;
}
.sp-wrapper {
margin: 1rem 0;
}
@@ -65,9 +65,17 @@ export class UserDetailComponent implements OnInit {
private _location: Location,
private dialog: MatDialog,
private router: Router,
activatedRoute: ActivatedRoute,
private mediaMatcher: MediaMatcher,
breadcrumbService: BreadcrumbService,
) {
activatedRoute.queryParams.pipe(take(1)).subscribe((params: Params) => {
const { id } = params;
if (id) {
this.currentSetting = id;
}
});
breadcrumbService.setBreadcrumb([
new Breadcrumb({
type: BreadcrumbType.ORG,
@@ -3,14 +3,17 @@
<ng-container *ngSwitchCase="Type.TYPE_HUMAN">
<div class="users-title-row">
<h1>{{ 'USER.PAGES.LIST' | translate }}</h1>
<a mat-icon-button href="https://docs.zitadel.com/docs/concepts/structure/users" rel="noreferrer"
target="_blank">
<a mat-icon-button href="https://docs.zitadel.com/docs/concepts/structure/users" rel="noreferrer" target="_blank">
<i class="las la-info-circle"></i>
</a>
</div>
<p class="user-list-sub cnsl-secondary-text">{{ 'USER.PAGES.DESCRIPTION' | translate }}</p>
<cnsl-user-table [type]="Type.TYPE_HUMAN" [disabled]="(['user.write$'] | hasRole | async) === false">
<cnsl-user-table
[type]="Type.TYPE_HUMAN"
[canWrite]="!!(['user.write$'] | hasRole | async)"
[canDelete]="!!(['user.delete$'] | hasRole | async)"
>
</cnsl-user-table>
</ng-container>
@@ -18,8 +21,12 @@
<h1>{{ 'USER.PAGES.LISTMACHINE' | translate }}</h1>
<p class="user-list-sub cnsl-secondary-text">{{ 'USER.PAGES.DESCRIPTIONMACHINE' | translate }}</p>
<cnsl-user-table [type]="Type.TYPE_MACHINE" [disabled]="(['user.write$'] | hasRole | async) === false">
<cnsl-user-table
[type]="Type.TYPE_MACHINE"
[canWrite]="!!(['user.write$'] | hasRole | async)"
[canDelete]="!!(['user.delete$'] | hasRole | async)"
>
</cnsl-user-table>
</ng-container>
</div>
</div>
</div>
@@ -14,6 +14,6 @@
}
.user-list-sub {
margin-bottom: 2rem;
margin-bottom: 1.5rem;
font-size: 14px;
}
@@ -1,35 +1,64 @@
<cnsl-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="totalResult"
[hideRefresh]="true" [timestamp]="viewTimestamp" [selection]="selection"
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes" [showBorder]="true">
<cnsl-refresh-table
[loading]="loading$ | async"
(refreshed)="refreshPage()"
[dataSize]="totalResult"
[hideRefresh]="true"
[timestamp]="viewTimestamp"
[selection]="selection"
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes"
[showBorder]="true"
>
<div leftActions class="user-table-left-actions">
<button class="type-button" [ngClass]="{'active': type === Type.TYPE_HUMAN}"
(click)="setType(Type.TYPE_HUMAN)">{{'USER.TABLE.TYPES.HUMAN' | translate}}</button>
<button class="type-button" [ngClass]="{'active': type === Type.TYPE_MACHINE}"
(click)="setType(Type.TYPE_MACHINE)">{{'USER.TABLE.TYPES.MACHINE' | translate}}</button>
<button class="type-button" [ngClass]="{ active: type === Type.TYPE_HUMAN }" (click)="setType(Type.TYPE_HUMAN)">
{{ 'USER.TABLE.TYPES.HUMAN' | translate }}
</button>
<button class="type-button" [ngClass]="{ active: type === Type.TYPE_MACHINE }" (click)="setType(Type.TYPE_MACHINE)">
{{ 'USER.TABLE.TYPES.MACHINE' | translate }}
</button>
</div>
<ng-template cnslHasRole [hasRole]="['user.write']" actions>
<button (click)="deactivateSelectedUsers()" class="cnsl-action-button bg-state inactive" mat-raised-button
*ngIf="selection.hasValue() && multipleDeactivatePossible" [disabled]="disabled" color="primary">
<span class="">{{'USER.TABLE.DEACTIVATE' | translate}}</span>
<cnsl-action-keys (actionTriggered)="deactivateSelectedUsers()" [type]="ActionKeysType.DEACTIVATE">
</cnsl-action-keys>
<button
(click)="deactivateSelectedUsers()"
class="cnsl-action-button bg-state inactive"
mat-raised-button
*ngIf="selection.hasValue() && multipleDeactivatePossible"
[disabled]="!canWrite"
color="primary"
>
<span class="">{{ 'USER.TABLE.DEACTIVATE' | translate }}</span>
<cnsl-action-keys (actionTriggered)="deactivateSelectedUsers()" [type]="ActionKeysType.DEACTIVATE"> </cnsl-action-keys>
</button>
<button (click)="reactivateSelectedUsers()" class="cnsl-action-button bg-state active margin-left" mat-raised-button
*ngIf="selection.hasValue() && multipleActivatePossible" [disabled]="disabled" color="primary">
<span class="">{{'USER.TABLE.ACTIVATE' | translate}}</span>
<cnsl-action-keys (actionTriggered)="reactivateSelectedUsers()" [type]="ActionKeysType.REACTIVATE">
</cnsl-action-keys>
<button
(click)="reactivateSelectedUsers()"
class="cnsl-action-button bg-state active margin-left"
mat-raised-button
*ngIf="selection.hasValue() && multipleActivatePossible"
[disabled]="!canWrite"
color="primary"
>
<span class="">{{ 'USER.TABLE.ACTIVATE' | translate }}</span>
<cnsl-action-keys (actionTriggered)="reactivateSelectedUsers()" [type]="ActionKeysType.REACTIVATE"> </cnsl-action-keys>
</button>
<cnsl-filter-user *ngIf="!selection.hasValue()" (filterChanged)="applySearchQuery($any($event))"
(filterOpen)="filterOpen = $event"></cnsl-filter-user>
<a *ngIf="!selection.hasValue()" [routerLink]="[ '/users',type === Type.TYPE_HUMAN ? 'create' : 'create-machine']"
color="primary" mat-raised-button [disabled]="disabled" class="cnsl-action-button">
<cnsl-filter-user
*ngIf="!selection.hasValue()"
(filterChanged)="applySearchQuery($any($event))"
(filterOpen)="filterOpen = $event"
></cnsl-filter-user>
<a
*ngIf="!selection.hasValue()"
[routerLink]="['/users', type === Type.TYPE_HUMAN ? 'create' : 'create-machine']"
color="primary"
mat-raised-button
[disabled]="!canWrite"
class="cnsl-action-button"
>
<mat-icon class="icon">add</mat-icon>
<span>{{ 'ACTIONS.NEW' | translate }}</span>
<cnsl-action-keys *ngIf="!filterOpen"
(actionTriggered)="gotoRouterLink([ '/users',type === Type.TYPE_HUMAN ? 'create' : 'create-machine'])">
<cnsl-action-keys
*ngIf="!filterOpen"
(actionTriggered)="gotoRouterLink(['/users', type === Type.TYPE_HUMAN ? 'create' : 'create-machine'])"
>
</cnsl-action-keys>
</a>
</ng-template>
@@ -38,19 +67,37 @@
<table class="table" mat-table [dataSource]="dataSource" matSort (matSortChange)="sortChange($event)">
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef class="selection">
<mat-checkbox [disabled]="disabled" color="primary" (change)="$event ? masterToggle() : null"
<mat-checkbox
[disabled]="!canWrite"
color="primary"
(change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
[indeterminate]="selection.hasValue() && !isAllSelected()"
>
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let user" class="selection">
<mat-checkbox [disabled]="disabled" color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(user) : null" [checked]="selection.isSelected(user)">
<mat-checkbox
[disabled]="!canWrite"
color="primary"
(click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(user) : null"
[checked]="selection.isSelected(user)"
>
<cnsl-avatar
*ngIf="user.human && user.human.profile.displayName && user.human?.profile.firstName && user.human?.profile.lastName; else cog"
class="avatar" [name]="user.human.profile.displayName" [avatarUrl]="user.human?.profile?.avatarUrl || ''"
[forColor]="user?.preferredLoginName" [size]="32">
*ngIf="
user.human &&
user.human.profile.displayName &&
user.human?.profile.firstName &&
user.human?.profile.lastName;
else cog
"
class="avatar"
[name]="user.human.profile.displayName"
[avatarUrl]="user.human?.profile?.avatarUrl || ''"
[forColor]="user?.preferredLoginName"
[size]="32"
>
</cnsl-avatar>
<ng-template #cog>
<cnsl-avatar [forColor]="user?.preferredLoginName" [isMachine]="true">
@@ -62,58 +109,74 @@
</ng-container>
<ng-container matColumnDef="displayName">
<th mat-header-cell *matHeaderCellDef mat-sort-header
[ngClass]="{'search-active': this.userSearchKey === UserListSearchKey.DISPLAY_NAME}">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
[ngClass]="{ 'search-active': this.userSearchKey === UserListSearchKey.DISPLAY_NAME }"
>
{{ 'USER.PROFILE.DISPLAYNAME' | translate }}
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span *ngIf="user.human">{{user.human.profile?.displayName}}</span>
<span *ngIf="user.machine">{{user.machine.name}}</span>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
<span *ngIf="user.human">{{ user.human.profile?.displayName }}</span>
<span *ngIf="user.machine">{{ user.machine.name }}</span>
</td>
</ng-container>
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef mat-sort-header
[ngClass]="{'search-active': this.userSearchKey === UserListSearchKey.USER_NAME}">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
[ngClass]="{ 'search-active': this.userSearchKey === UserListSearchKey.USER_NAME }"
>
{{ 'USER.PROFILE.USERNAME' | translate }}
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user.userName}} </td>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
{{ user.userName }}
</td>
</ng-container>
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef mat-sort-header
[ngClass]="{'search-active': this.UserListSearchKey === UserListSearchKey.EMAIL}">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
[ngClass]="{ 'search-active': this.UserListSearchKey === UserListSearchKey.EMAIL }"
>
{{ 'USER.EMAIL' | translate }}
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span *ngIf="user.human?.email?.email">{{user.human?.email.email}}</span>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
<span *ngIf="user.human?.email?.email">{{ user.human?.email.email }}</span>
</td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'USER.DATA.STATE' | translate }} </th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span class="state"
[ngClass]="{'active': user.state === UserState.USER_STATE_ACTIVE, 'inactive': user.state === UserState.USER_STATE_INACTIVE}">
{{ 'USER.DATA.STATE'+user.state | translate }}
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'USER.DATA.STATE' | translate }}</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
<span
class="state"
[ngClass]="{
active: user.state === UserState.USER_STATE_ACTIVE,
inactive: user.state === UserState.USER_STATE_INACTIVE
}"
>
{{ 'USER.DATA.STATE' + user.state | translate }}
</span>
</td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{'USER.TABLE.CREATIONDATE' | translate}} </th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span class="no-break">{{user.details.creationDate | timestampToDate | localizedDate: 'fromNow'
}}</span>
<th mat-header-cell *matHeaderCellDef>{{ 'USER.TABLE.CREATIONDATE' | translate }}</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
<span class="no-break">{{ user.details.creationDate | timestampToDate | localizedDate: 'fromNow' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.TABLE.CHANGEDATE' | translate }} </th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span class="no-break">{{user.details.changeDate | timestampToDate | localizedDate: 'fromNow'
}}</span>
<th mat-header-cell *matHeaderCellDef>{{ 'USER.TABLE.CHANGEDATE' | translate }}</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
<span class="no-break">{{ user.details.changeDate | timestampToDate | localizedDate: 'fromNow' }}</span>
</td>
</ng-container>
@@ -121,28 +184,41 @@
<th mat-header-cell *matHeaderCellDef class="user-tr-actions"></th>
<td mat-cell *matCellDef="let user" class="user-tr-actions">
<cnsl-table-actions>
<button actions matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" (click)="deleteUser(user)"
mat-icon-button>
<button
actions
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
color="warn"
(click)="deleteUser(user)"
[disabled]="!canWrite || !canDelete"
mat-icon-button
>
<i class="las la-trash"></i>
</button>
</cnsl-table-actions>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="type === Type.TYPE_HUMAN ? displayedColumnsHuman : displayedColumnsMachine">
</tr>
<tr class="highlight pointer" mat-row
*matRowDef="let user; columns: (type === Type.TYPE_HUMAN ? displayedColumnsHuman : displayedColumnsMachine);">
</tr>
<tr mat-header-row *matHeaderRowDef="type === Type.TYPE_HUMAN ? displayedColumnsHuman : displayedColumnsMachine"></tr>
<tr
class="highlight pointer"
mat-row
*matRowDef="let user; columns: type === Type.TYPE_HUMAN ? displayedColumnsHuman : displayedColumnsMachine"
></tr>
</table>
</div>
<div *ngIf="(loading$ | async) === false && !dataSource?.data?.length" class="no-content-row">
<i class="las la-exclamation"></i>
<span>{{'USER.TABLE.EMPTY' | translate}}</span>
<span>{{ 'USER.TABLE.EMPTY' | translate }}</span>
</div>
<cnsl-paginator #paginator class="paginator" [timestamp]="viewTimestamp" [length]="totalResult || 0"
[pageSize]="INITIAL_PAGE_SIZE" [timestamp]="viewTimestamp" [pageSizeOptions]="[10, 20, 50, 100]"
(page)="changePage($event)"></cnsl-paginator>
</cnsl-refresh-table>
<cnsl-paginator
#paginator
class="paginator"
[timestamp]="viewTimestamp"
[length]="totalResult || 0"
[pageSize]="INITIAL_PAGE_SIZE"
[timestamp]="viewTimestamp"
[pageSizeOptions]="[10, 20, 50, 100]"
(page)="changePage($event)"
></cnsl-paginator>
</cnsl-refresh-table>
@@ -16,7 +16,7 @@
opacity: 0.6;
font-size: 15px;
cursor: pointer;
color: map-get($foreground, base);
color: map-get($foreground, text);
&:first-child {
margin-right: 1rem;
@@ -37,7 +37,7 @@ export class UserTableComponent implements OnInit {
public Type: any = Type;
@Input() public type: Type = Type.TYPE_HUMAN;
@Input() refreshOnPreviousRoutes: string[] = [];
@Input() disabled: boolean = false;
@Input() canWrite: boolean = false;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
@ViewChild(MatSort) public sort!: MatSort;
public INITIAL_PAGE_SIZE: number = 20;
@@ -77,6 +77,7 @@ export class UserTableComponent implements OnInit {
public filterOpen: boolean = false;
private searchQueries: SearchQuery[] = [];
@Input() public canDelete: boolean = false;
constructor(
private router: Router,
public translate: TranslateService,