mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-26 09:12:16 +00:00
feat: rehauled console (#3525)
* new console * move npm ci to angular build * rel path for assets * local grpc copy * login policy, rm clear views, features rel path * lock Co-authored-by: Livio Amstutz <[email protected]>
This commit is contained in:
co-authored by
Livio Amstutz
parent
00f7dbe875
commit
08ae39ae19
@@ -1,45 +1,44 @@
|
||||
<div class="change-header">
|
||||
<span class="ch-header">{{ 'CHANGES.LISTTITLE' | translate }}</span>
|
||||
<button matTooltip="{{'ACTIONS.REFRESH' | translate}}" (click)="init()" mat-icon-button>
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<span class="ch-header">{{ 'CHANGES.LISTTITLE' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="scroll-container" cnslScrollable (scrollPosition)="scrollHandler($event)">
|
||||
<li class="item change-item-back" *ngFor="let hist of data | async; index as histindex">
|
||||
<span *ngIf="hist.values[0].dates[0]" class="date">
|
||||
{{ hist.values[0].dates[0]| timestampToDate | localizedDate: 'dd. MMMM YYYY' }}
|
||||
</span>
|
||||
<div class="item" *ngFor="let dayelement of hist.values; index as i">
|
||||
<div class="row">
|
||||
<cnsl-avatar matTooltip="{{ dayelement.editorDisplayName }}"
|
||||
*ngIf="dayelement.editorDisplayName; else spacer" class="avatar"
|
||||
[name]="dayelement.editorDisplayName" [size]="32" [forColor]="dayelement?.editorPreferredLoginName ?? 'A'"
|
||||
[avatarUrl]="dayelement.editorAvatarUrl || ''">
|
||||
</cnsl-avatar>
|
||||
<ng-template #spacer>
|
||||
<div class="spacer"></div>
|
||||
</ng-template>
|
||||
<div class="actions">
|
||||
<div class="action" *ngFor="let action of dayelement.eventTypes; index as j">
|
||||
<button disabled mat-icon-button aria-label="Restore history"
|
||||
matTooltip="{{ dayelement.dates[j] | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}">
|
||||
<mat-icon class="icon">schedule</mat-icon>
|
||||
</button>
|
||||
|
||||
<span>
|
||||
<span class="msg">{{ action.localizedMessage }}</span>
|
||||
<span class="block">{{
|
||||
dayelement.dates[j] | timestampToDate | localizedDate: 'HH:mm'
|
||||
}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroll-container">
|
||||
<li class="item" *ngFor="let hist of data | async; index as histindex">
|
||||
<span *ngIf="hist.values[0].dates[0]" class="date">
|
||||
{{ hist.values[0].dates[0]| timestampToDate | localizedDate: 'dd. MMM YYYY' }}
|
||||
</span>
|
||||
<div class="item" *ngFor="let dayelement of hist.values; index as i">
|
||||
<div class="row">
|
||||
<cnsl-avatar matTooltip="{{ dayelement.editorDisplayName }}" *ngIf="dayelement.editorDisplayName; else spacer"
|
||||
class="avatar" [name]="dayelement.editorDisplayName" [size]="32"
|
||||
[forColor]="dayelement?.editorPreferredLoginName ?? 'A'" [avatarUrl]="dayelement.editorAvatarUrl || ''">
|
||||
</cnsl-avatar>
|
||||
<ng-template #spacer>
|
||||
<div class="spacer"></div>
|
||||
</ng-template>
|
||||
<div class="change-actions">
|
||||
<div class="change-action" *ngFor="let action of dayelement.eventTypes; index as j">
|
||||
<div>
|
||||
<span class="msg">{{ action.localizedMessage }}</span>
|
||||
<span class="block"
|
||||
matTooltip="{{ dayelement.dates[j] | timestampToDate | localizedDate: 'dd. MM YYYY, HH:mm' }}">{{
|
||||
dayelement.dates[j] | timestampToDate | localizedDate: 'HH:mm'
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<div class="sp-wrapper">
|
||||
<mat-spinner *ngIf="loading | async" diameter="25"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
<span class="end-container" *ngIf="bottom">{{'CHANGES.BOTTOM' | translate}}</span>
|
||||
</div>
|
||||
</li>
|
||||
<div class="ch-sp-wrapper">
|
||||
<mat-spinner *ngIf="loading | async" diameter="25"></mat-spinner>
|
||||
</div>
|
||||
|
||||
<button (click)="more()" class="load-more-button" *ngIf="!bottom" mat-stroked-button>
|
||||
{{'CHANGES.LOADMORE' | translate}}
|
||||
</button>
|
||||
|
||||
<div *ngIf="bottom" class="end-container">
|
||||
<span class="cnsl-secondary-text">{{'CHANGES.BOTTOM' | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -11,7 +11,7 @@
|
||||
font-weight: 400;
|
||||
margin-top: 1rem;
|
||||
font-size: 14px;
|
||||
letter-spacing: .05em;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@@ -23,24 +23,25 @@
|
||||
@mixin changes-theme($theme) {
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$secondary-text: map-get($foreground, secondary-text);
|
||||
|
||||
.scroll-container {
|
||||
max-height: 50vh;
|
||||
overflow-y: scroll;
|
||||
border-bottom: 1px solid map-get($foreground, divider);
|
||||
margin-bottom: .5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
box-sizing: border-box;
|
||||
|
||||
.date {
|
||||
font-weight: 500;
|
||||
font-size: .8rem;
|
||||
font-size: 0.8rem;
|
||||
display: block;
|
||||
margin-bottom: .5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: block;
|
||||
padding: 10px 0;
|
||||
font-size: .8rem;
|
||||
font-size: 0.8rem;
|
||||
box-sizing: border-box;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
@@ -50,31 +51,25 @@
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
.change-actions {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: -.5rem;
|
||||
margin-top: -0.25rem;
|
||||
margin-left: 1rem;
|
||||
|
||||
.action {
|
||||
.change-action {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
.icon {
|
||||
width: 32px;
|
||||
display: inline-block;
|
||||
height: 1.2rem;
|
||||
line-height: 1.2rem;
|
||||
font-size: 1.2rem;
|
||||
color: var(--grey);
|
||||
}
|
||||
padding: 0.25rem 0;
|
||||
cursor: default;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
font-size: .8rem;
|
||||
font-size: 0.8rem;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@@ -84,60 +79,32 @@
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.restore {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
margin-left: 1rem;
|
||||
transform: opacity .2s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.restore {
|
||||
visibility: visible;
|
||||
display: inline-block;
|
||||
opacity: 1;
|
||||
color: #81868a;
|
||||
|
||||
&[disabled] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
color: $secondary-text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-disable */
|
||||
$primary: map-get($theme, primary);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$transparent-color: if($is-dark-theme, rgba(#8795a1, .2), rgba(#8795a1, .2));
|
||||
|
||||
/* stylelint-enable */
|
||||
|
||||
&.change-item-back {
|
||||
background-color: rgba($transparent-color, .93);
|
||||
transition: background-color .3s cubic-bezier(.645, .045, .355, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sp-wrapper {
|
||||
padding: .5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.ch-sp-wrapper {
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.load-more-button {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.end-container {
|
||||
font-size: 14px;
|
||||
margin: 1rem 0 1rem 0;
|
||||
display: block;
|
||||
color: var(--grey);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
span {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,16 +32,17 @@ export interface MappedChange {
|
||||
editorDisplayName: string;
|
||||
editorAvatarUrl: string;
|
||||
editorPreferredLoginName: string;
|
||||
eventTypes: Array<{ key: string; localizedMessage: string; }>;
|
||||
eventTypes: Array<{ key: string; localizedMessage: string }>;
|
||||
sequences: number[];
|
||||
}>;
|
||||
}
|
||||
|
||||
type ListChanges = ListMyUserChangesResponse.AsObject |
|
||||
ListUserChangesResponse.AsObject |
|
||||
ListProjectChangesResponse.AsObject |
|
||||
ListOrgChangesResponse.AsObject |
|
||||
ListAppChangesResponse.AsObject;
|
||||
type ListChanges =
|
||||
| ListMyUserChangesResponse.AsObject
|
||||
| ListUserChangesResponse.AsObject
|
||||
| ListProjectChangesResponse.AsObject
|
||||
| ListOrgChangesResponse.AsObject
|
||||
| ListAppChangesResponse.AsObject;
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-changes',
|
||||
@@ -64,9 +65,7 @@ export class ChangesComponent implements OnInit, OnDestroy {
|
||||
public data!: Observable<MappedChange[]>;
|
||||
public changes!: ListChanges;
|
||||
private destroyed$: Subject<void> = new Subject();
|
||||
constructor(private mgmtUserService: ManagementService, private authUserService: GrpcAuthService) {
|
||||
|
||||
}
|
||||
constructor(private mgmtUserService: ManagementService, private authUserService: GrpcAuthService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.init();
|
||||
@@ -82,24 +81,23 @@ export class ChangesComponent implements OnInit, OnDestroy {
|
||||
this.destroyed$.next();
|
||||
}
|
||||
|
||||
public scrollHandler(e: any): void {
|
||||
if (e === 'bottom') {
|
||||
this.more();
|
||||
}
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
let first: Promise<ListChanges>;
|
||||
switch (this.changeType) {
|
||||
case ChangeType.MYUSER: first = this.authUserService.listMyUserChanges(20, 0);
|
||||
case ChangeType.MYUSER:
|
||||
first = this.authUserService.listMyUserChanges(30, 0);
|
||||
break;
|
||||
case ChangeType.USER: first = this.mgmtUserService.listUserChanges(this.id, 20, 0);
|
||||
case ChangeType.USER:
|
||||
first = this.mgmtUserService.listUserChanges(this.id, 30, 0);
|
||||
break;
|
||||
case ChangeType.PROJECT: first = this.mgmtUserService.listProjectChanges(this.id, 20, 0);
|
||||
case ChangeType.PROJECT:
|
||||
first = this.mgmtUserService.listProjectChanges(this.id, 30, 0);
|
||||
break;
|
||||
case ChangeType.ORG: first = this.mgmtUserService.listOrgChanges(20, 0);
|
||||
case ChangeType.ORG:
|
||||
first = this.mgmtUserService.listOrgChanges(30, 0);
|
||||
break;
|
||||
case ChangeType.APP: first = this.mgmtUserService.listAppChanges(this.id, this.secId, 20, 0);
|
||||
case ChangeType.APP:
|
||||
first = this.mgmtUserService.listAppChanges(this.id, this.secId, 30, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -109,24 +107,30 @@ export class ChangesComponent implements OnInit, OnDestroy {
|
||||
this.data = this._data.asObservable().pipe(
|
||||
scan((acc, val) => {
|
||||
return false ? val.concat(acc) : acc.concat(val);
|
||||
}));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private more(): void {
|
||||
public more(): void {
|
||||
const cursor = this.getCursor();
|
||||
|
||||
let more: Promise<ListChanges>;
|
||||
|
||||
switch (this.changeType) {
|
||||
case ChangeType.MYUSER: more = this.authUserService.listMyUserChanges(20, cursor);
|
||||
case ChangeType.MYUSER:
|
||||
more = this.authUserService.listMyUserChanges(20, cursor);
|
||||
break;
|
||||
case ChangeType.USER: more = this.mgmtUserService.listUserChanges(this.id, 20, cursor);
|
||||
case ChangeType.USER:
|
||||
more = this.mgmtUserService.listUserChanges(this.id, 20, cursor);
|
||||
break;
|
||||
case ChangeType.PROJECT: more = this.mgmtUserService.listProjectChanges(this.id, 20, cursor);
|
||||
case ChangeType.PROJECT:
|
||||
more = this.mgmtUserService.listProjectChanges(this.id, 20, cursor);
|
||||
break;
|
||||
case ChangeType.ORG: more = this.mgmtUserService.listOrgChanges(20, cursor);
|
||||
case ChangeType.ORG:
|
||||
more = this.mgmtUserService.listOrgChanges(20, cursor);
|
||||
break;
|
||||
case ChangeType.APP: more = this.mgmtUserService.listAppChanges(this.id, this.secId, 20, cursor);
|
||||
case ChangeType.APP:
|
||||
more = this.mgmtUserService.listAppChanges(this.id, this.secId, 20, cursor);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -147,42 +151,45 @@ export class ChangesComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Maps the snapshot to usable format the updates source
|
||||
private mapAndUpdate(col: Promise<ListChanges>): any {
|
||||
if (this._done.value || this._loading.value) { return; }
|
||||
if (this._done.value || this._loading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Map snapshot with doc ref (needed for cursor)
|
||||
if (!this.bottom) {
|
||||
// loading
|
||||
this._loading.next(true);
|
||||
|
||||
return from(col).pipe(
|
||||
take(1),
|
||||
tap((res: ListChanges) => {
|
||||
const values = res.resultList;
|
||||
const mapped = this.mapChanges(values);
|
||||
// update source with new values, done loading
|
||||
// this._data.next(values);
|
||||
this._data.next(mapped);
|
||||
return from(col)
|
||||
.pipe(
|
||||
take(1),
|
||||
tap((res: ListChanges) => {
|
||||
const values = res.resultList;
|
||||
const mapped = this.mapChanges(values);
|
||||
|
||||
this._loading.next(false);
|
||||
this._data.next(mapped);
|
||||
|
||||
// no more values, mark done
|
||||
if (!values.length) {
|
||||
this._done.next(true);
|
||||
}
|
||||
}),
|
||||
catchError(_ => {
|
||||
this._loading.next(false);
|
||||
this.bottom = true;
|
||||
return of([]);
|
||||
}),
|
||||
).subscribe();
|
||||
this._loading.next(false);
|
||||
|
||||
if (!values.length) {
|
||||
this._done.next(true);
|
||||
}
|
||||
}),
|
||||
catchError((_) => {
|
||||
this._loading.next(false);
|
||||
this.bottom = true;
|
||||
return of([]);
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
private mapChanges(changes: Change.AsObject[]): {
|
||||
key: string; values: any[];
|
||||
key: string;
|
||||
values: any[];
|
||||
}[] {
|
||||
const splitted: { [editorId: string]: any[]; } = {};
|
||||
const splitted: { [editorId: string]: any[] } = {};
|
||||
changes.forEach((change) => {
|
||||
if (change.changeDate) {
|
||||
const index = `${this.getDateString(change.changeDate)}`;
|
||||
@@ -230,7 +237,7 @@ export class ChangesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
});
|
||||
const arr = Object.keys(splitted).map(key => {
|
||||
const arr = Object.keys(splitted).map((key) => {
|
||||
return { key: key, values: splitted[key] };
|
||||
});
|
||||
|
||||
@@ -263,7 +270,7 @@ export class ChangesComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Order by descending property key
|
||||
keyDescOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
|
||||
return a.key > b.key ? -1 : (b.key > a.key ? 1 : 0);
|
||||
return a.key > b.key ? -1 : b.key > a.key ? 1 : 0;
|
||||
};
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user