1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-25 12:15:18 +01:00

AC-2410 Migrate Events Component (#8970)

* AC-2410 Migrate Events Component

* AC-2410 Addressed a minor correction
This commit is contained in:
KiruthigaManivannan 2024-05-23 18:59:24 +05:30 committed by GitHub
parent ad3c40297f
commit dd53a1c5ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 144 additions and 171 deletions

View File

@ -1,4 +1,5 @@
import { Directive } from "@angular/core"; import { Directive } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { EventResponse } from "@bitwarden/common/models/response/event.response"; import { EventResponse } from "@bitwarden/common/models/response/event.response";
import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response";
@ -16,16 +17,16 @@ export abstract class BaseEventsComponent {
loading = true; loading = true;
loaded = false; loaded = false;
events: EventView[]; events: EventView[];
start: string;
end: string;
dirtyDates = true; dirtyDates = true;
continuationToken: string; continuationToken: string;
refreshPromise: Promise<any>;
exportPromise: Promise<any>;
morePromise: Promise<any>;
abstract readonly exportFileName: string; abstract readonly exportFileName: string;
protected eventsForm = new FormGroup({
start: new FormControl(null),
end: new FormControl(null),
});
constructor( constructor(
protected eventService: EventService, protected eventService: EventService,
protected i18nService: I18nService, protected i18nService: I18nService,
@ -39,8 +40,32 @@ export abstract class BaseEventsComponent {
this.end = defaultDates[1]; this.end = defaultDates[1];
} }
async exportEvents() { get start(): string {
if (this.appApiPromiseUnfulfilled() || this.dirtyDates) { return this.eventsForm.value.start;
}
set start(val: string) {
this.eventsForm.get("start").setValue(val);
}
get end(): string {
return this.eventsForm.value.end;
}
set end(val: string) {
this.eventsForm.get("end").setValue(val);
}
loadMoreEvents = async () => {
await this.loadEvents(false);
};
refreshEvents = async () => {
await this.loadEvents(true);
};
exportEvents = async () => {
if (this.dirtyDates) {
return; return;
} }
@ -51,23 +76,19 @@ export abstract class BaseEventsComponent {
return; return;
} }
let promise: Promise<any>;
try { try {
this.exportPromise = this.export(dates[0], dates[1]); promise = this.export(dates[0], dates[1]);
await promise;
await this.exportPromise;
} catch (e) { } catch (e) {
this.logService.error(`Handled exception: ${e}`); this.logService.error(`Handled exception: ${e}`);
} }
this.exportPromise = null; promise = null;
this.loading = false; this.loading = false;
} };
async loadEvents(clearExisting: boolean) {
if (this.appApiPromiseUnfulfilled()) {
return;
}
loadEvents = async (clearExisting: boolean) => {
const dates = this.parseDates(); const dates = this.parseDates();
if (dates == null) { if (dates == null) {
return; return;
@ -75,17 +96,14 @@ export abstract class BaseEventsComponent {
this.loading = true; this.loading = true;
let events: EventView[] = []; let events: EventView[] = [];
let promise: Promise<any>;
try { try {
const promise = this.loadAndParseEvents( promise = this.loadAndParseEvents(
dates[0], dates[0],
dates[1], dates[1],
clearExisting ? null : this.continuationToken, clearExisting ? null : this.continuationToken,
); );
if (clearExisting) {
this.refreshPromise = promise;
} else {
this.morePromise = promise;
}
const result = await promise; const result = await promise;
this.continuationToken = result.continuationToken; this.continuationToken = result.continuationToken;
events = result.events; events = result.events;
@ -101,9 +119,8 @@ export abstract class BaseEventsComponent {
this.dirtyDates = false; this.dirtyDates = false;
this.loading = false; this.loading = false;
this.morePromise = null; promise = null;
this.refreshPromise = null; };
}
protected abstract requestEvents( protected abstract requestEvents(
startDate: string, startDate: string,
@ -161,10 +178,6 @@ export abstract class BaseEventsComponent {
return dates; return dates;
} }
protected appApiPromiseUnfulfilled() {
return this.refreshPromise != null || this.morePromise != null || this.exportPromise != null;
}
private async export(start: string, end: string) { private async export(start: string, end: string) {
let continuationToken = this.continuationToken; let continuationToken = this.continuationToken;
let events = [].concat(this.events); let events = [].concat(this.events);

View File

@ -1,6 +1,6 @@
<app-header></app-header> <app-header></app-header>
<div class="tw-mb-4"> <div class="tw-mb-4" [formGroup]="eventsForm">
<div class="tw-mt-4 tw-flex tw-items-center"> <div class="tw-mt-4 tw-flex tw-items-center">
<bit-form-field> <bit-form-field>
<bit-label>{{ "from" | i18n }}</bit-label> <bit-label>{{ "from" | i18n }}</bit-label>
@ -8,7 +8,7 @@
bitInput bitInput
type="datetime-local" type="datetime-local"
placeholder="{{ 'startDate' | i18n }}" placeholder="{{ 'startDate' | i18n }}"
[(ngModel)]="start" formControlName="start"
(change)="dirtyDates = true" (change)="dirtyDates = true"
/> />
</bit-form-field> </bit-form-field>
@ -19,51 +19,44 @@
bitInput bitInput
type="datetime-local" type="datetime-local"
placeholder="{{ 'endDate' | i18n }}" placeholder="{{ 'endDate' | i18n }}"
[(ngModel)]="end" formControlName="end"
(change)="dirtyDates = true" (change)="dirtyDates = true"
/> />
</bit-form-field> </bit-form-field>
<form #refreshForm [appApiAction]="refreshPromise"> <form>
<button <button
class="tw-mx-3 tw-mt-1" class="tw-mx-3 tw-mt-1"
type="button" type="button"
bitButton bitButton
bitFormButton
buttonType="primary" buttonType="primary"
(click)="loadEvents(true)" [bitAction]="refreshEvents"
[disabled]="loaded && refreshForm.loading"
> >
{{ "update" | i18n }} {{ "update" | i18n }}
</button> </button>
</form> </form>
<form #exportForm [appApiAction]="exportPromise"> <form>
<button <button
type="button" type="button"
class="tw-mt-1" class="tw-mt-1"
bitButton bitButton
[ngClass]="{ loading: exportForm.loading }" bitFormButton
(click)="exportEvents()" [bitAction]="exportEvents"
[disabled]="(loaded && exportForm.loading) || dirtyDates" [disabled]="dirtyDates"
> >
<span>{{ "export" | i18n }}</span> <span>{{ "export" | i18n }}</span>
<i <i class="bwi bwi-fw bwi-sign-in" aria-hidden="true"></i>
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-sign-in': !exportForm.loading,
'bwi-spinner bwi-spin': exportForm.loading
}"
></i>
</button> </button>
</form> </form>
</div> </div>
</div> </div>
<ng-container *ngIf="!loaded"> <ng-container *ngIf="!loaded">
<i <i
class="bwi bwi-spinner bwi-spin text-muted" class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<span class="sr-only">{{ "loading" | i18n }}</span> <span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p> <p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p>
@ -90,21 +83,12 @@
</ng-template> </ng-template>
</bit-table> </bit-table>
<button <button
#moreBtn
[appApiAction]="morePromise"
type="button" type="button"
bitButton bitButton
buttonType="primary" buttonType="primary"
(click)="loadEvents(false)" [bitAction]="loadMoreEvents"
[disabled]="loaded && $any(moreBtn).loading"
*ngIf="continuationToken" *ngIf="continuationToken"
> >
<i {{ "loadMore" | i18n }}
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="moreBtn.loading"
></i>
<span>{{ "loadMore" | i18n }}</span>
</button> </button>
</ng-container> </ng-container>

View File

@ -108,7 +108,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
} }
} }
await this.loadEvents(true); await this.refreshEvents();
this.loaded = true; this.loaded = true;
} }

View File

@ -1,104 +1,97 @@
<app-header></app-header> <app-header></app-header>
<div class="ml-auto d-flex"> <div class="tw-mb-4" [formGroup]="eventsForm">
<div class="form-inline"> <div class="tw-mt-4 tw-flex tw-items-center">
<label class="sr-only" for="start">{{ "startDate" | i18n }}</label> <bit-form-field>
<bit-label>{{ "startDate" | i18n }}</bit-label>
<input <input
bitInput
type="datetime-local" type="datetime-local"
class="form-control form-control-sm"
id="start"
placeholder="{{ 'startDate' | i18n }}" placeholder="{{ 'startDate' | i18n }}"
[(ngModel)]="start" formControlName="start"
placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true" (change)="dirtyDates = true"
/> />
<span class="mx-2">-</span> </bit-form-field>
<label class="sr-only" for="end">{{ "endDate" | i18n }}</label> <span class="tw-mx-2">-</span>
<bit-form-field>
<bit-label>{{ "endDate" | i18n }}</bit-label>
<input <input
bitInput
type="datetime-local" type="datetime-local"
class="form-control form-control-sm"
id="end"
placeholder="{{ 'endDate' | i18n }}" placeholder="{{ 'endDate' | i18n }}"
[(ngModel)]="end" formControlName="end"
placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true" (change)="dirtyDates = true"
/> />
</div> </bit-form-field>
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline"> <form>
<button <button
class="tw-mx-3 tw-mt-1"
type="button" type="button"
class="btn btn-sm btn-outline-primary ml-3" bitButton
(click)="loadEvents(true)" bitFormButton
[disabled]="loaded && refreshForm.loading" buttonType="primary"
[bitAction]="refreshEvents"
> >
<i
class="bwi bwi-refresh bwi-fw"
aria-hidden="true"
[ngClass]="{ 'bwi-spin': loaded && refreshForm.loading }"
></i>
{{ "refresh" | i18n }} {{ "refresh" | i18n }}
</button> </button>
</form> </form>
<form #exportForm [appApiAction]="exportPromise" class="d-inline"> <form>
<button <button
type="button" type="button"
class="btn btn-sm btn-outline-primary btn-submit manual ml-3" class="tw-mt-1"
[ngClass]="{ loading: exportForm.loading }" bitButton
(click)="exportEvents()" bitFormButton
[disabled]="(loaded && exportForm.loading) || dirtyDates" [bitAction]="exportEvents"
[disabled]="dirtyDates"
> >
<i class="bwi bwi-spinner bwi-spin" aria-hidden="true"></i>
<span>{{ "export" | i18n }}</span> <span>{{ "export" | i18n }}</span>
<i class="bwi bwi-fw bwi-sign-in" aria-hidden="true"></i>
</button> </button>
</form> </form>
</div> </div>
</div>
<ng-container *ngIf="!loaded"> <ng-container *ngIf="!loaded">
<i <i
class="bwi bwi-spinner bwi-spin text-muted" class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<span class="sr-only">{{ "loading" | i18n }}</span> <span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p> <p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p>
<table class="table table-hover" *ngIf="events && events.length"> <bit-table *ngIf="events && events.length">
<thead> <ng-container header>
<tr> <tr>
<th class="border-top-0" width="210">{{ "timestamp" | i18n }}</th> <th bitCell>{{ "timestamp" | i18n }}</th>
<th class="border-top-0" width="40"> <th bitCell>{{ "device" | i18n }}</th>
<span class="sr-only">{{ "device" | i18n }}</span> <th bitCell>{{ "user" | i18n }}</th>
</th> <th bitCell>{{ "event" | i18n }}</th>
<th class="border-top-0" width="150">{{ "user" | i18n }}</th>
<th class="border-top-0">{{ "event" | i18n }}</th>
</tr> </tr>
</thead> </ng-container>
<tbody> <ng-template body>
<tr *ngFor="let e of events"> <tr bitRow *ngFor="let e of events" alignContent="top">
<td>{{ e.date | date: "medium" }}</td> <td bitCell class="tw-whitespace-nowrap">{{ e.date | date: "medium" }}</td>
<td> <td bitCell>
<i <i
class="text-muted bwi bwi-lg {{ e.appIcon }}" class="tw-text-muted bwi bwi-lg {{ e.appIcon }}"
title="{{ e.appName }}, {{ e.ip }}" title="{{ e.appName }}, {{ e.ip }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<span class="sr-only">{{ e.appName }}, {{ e.ip }}</span> <span class="tw-sr-only">{{ e.appName }}, {{ e.ip }}</span>
</td> </td>
<td> <td bitCell>
<span title="{{ e.userEmail }}">{{ e.userName }}</span> <span title="{{ e.userEmail }}">{{ e.userName }}</span>
</td> </td>
<td [innerHTML]="e.message"></td> <td bitCell [innerHTML]="e.message"></td>
</tr> </tr>
</tbody> </ng-template>
</table> </bit-table>
<button <button
#moreBtn bitButton
[appApiAction]="morePromise"
type="button" type="button"
class="btn btn-block btn-link btn-submit" buttonType="primary"
(click)="loadEvents(false)" [bitAction]="loadMoreEvents"
[disabled]="loaded && $any(moreBtn).loading"
*ngIf="continuationToken" *ngIf="continuationToken"
> >
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>

View File

@ -70,7 +70,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
this.providerUsersIdMap.set(u.id, { name: name, email: u.email }); this.providerUsersIdMap.set(u.id, { name: name, email: u.email });
this.providerUsersUserIdMap.set(u.userId, { name: name, email: u.email }); this.providerUsersUserIdMap.set(u.userId, { name: name, email: u.email });
}); });
await this.loadEvents(true); await this.refreshEvents();
this.loaded = true; this.loaded = true;
} }

View File

@ -1,13 +1,13 @@
<div class="tw-mb-4"> <div class="tw-mb-4">
<h1>{{ "eventLogs" | i18n }}</h1> <h1>{{ "eventLogs" | i18n }}</h1>
<div class="tw-mt-4 tw-flex tw-items-center"> <div class="tw-mt-4 tw-flex tw-items-center" [formGroup]="eventsForm">
<bit-form-field> <bit-form-field>
<bit-label>{{ "from" | i18n }}</bit-label> <bit-label>{{ "from" | i18n }}</bit-label>
<input <input
bitInput bitInput
type="datetime-local" type="datetime-local"
placeholder="{{ 'startDate' | i18n }}" placeholder="{{ 'startDate' | i18n }}"
[(ngModel)]="start" formControlName="start"
(change)="dirtyDates = true" (change)="dirtyDates = true"
/> />
</bit-form-field> </bit-form-field>
@ -18,51 +18,43 @@
bitInput bitInput
type="datetime-local" type="datetime-local"
placeholder="{{ 'endDate' | i18n }}" placeholder="{{ 'endDate' | i18n }}"
[(ngModel)]="end" formControlName="end"
(change)="dirtyDates = true" (change)="dirtyDates = true"
/> />
</bit-form-field> </bit-form-field>
<form #refreshForm [appApiAction]="refreshPromise"> <form>
<button <button
class="tw-mx-3 tw-mt-1" class="tw-mx-3 tw-mt-1"
type="button" type="button"
bitButton bitButton
bitFormButton
buttonType="primary" buttonType="primary"
(click)="loadEvents(true)" [bitAction]="refreshEvents"
[disabled]="loaded && refreshForm.loading"
> >
{{ "update" | i18n }} {{ "update" | i18n }}
</button> </button>
</form> </form>
<form #exportForm [appApiAction]="exportPromise"> <form>
<button <button
type="button" type="button"
class="tw-mt-1" class="tw-mt-1"
bitButton bitButton
[ngClass]="{ loading: exportForm.loading }" [bitAction]="exportEvents"
(click)="exportEvents()" [disabled]="dirtyDates"
[disabled]="(loaded && exportForm.loading) || dirtyDates"
> >
<span>{{ "export" | i18n }}</span> <span>{{ "export" | i18n }}</span>
<i <i class="bwi bwi-fw bwi-sign-in" aria-hidden="true"></i>
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-sign-in': !exportForm.loading,
'bwi-spinner bwi-spin': exportForm.loading
}"
></i>
</button> </button>
</form> </form>
</div> </div>
</div> </div>
<ng-container *ngIf="!loaded"> <ng-container *ngIf="!loaded">
<i <i
class="bwi bwi-spinner bwi-spin text-muted" class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<span class="sr-only">{{ "loading" | i18n }}</span> <span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p> <p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p>
@ -85,21 +77,12 @@
</ng-template> </ng-template>
</bit-table> </bit-table>
<button <button
#moreBtn
[appApiAction]="morePromise"
type="button" type="button"
bitButton bitButton
buttonType="primary" buttonType="primary"
(click)="loadEvents(false)" [bitAction]="loadMoreEvents"
[disabled]="loaded && $any(moreBtn).loading"
*ngIf="continuationToken" *ngIf="continuationToken"
> >
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="moreBtn.loading"
></i>
<span>{{ "loadMore" | i18n }}</span> <span>{{ "loadMore" | i18n }}</span>
</button> </button>
</ng-container> </ng-container>

View File

@ -50,7 +50,7 @@ export class ServiceAccountEventsComponent extends BaseEventsComponent implement
} }
async load() { async load() {
await this.loadEvents(true); await this.refreshEvents();
this.loaded = true; this.loaded = true;
} }