1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-23 11:56:00 +01:00

user events

This commit is contained in:
Kyle Spearrin 2018-07-11 14:43:00 -04:00
parent 1f7ca7386a
commit 6d225beb46
7 changed files with 201 additions and 4 deletions

View File

@ -43,6 +43,7 @@ import { GroupsComponent as OrgGroupsComponent } from './organizations/manage/gr
import { ManageComponent as OrgManageComponent } from './organizations/manage/manage.component';
import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/people.component';
import { UserAddEditComponent as OrgUserAddEditComponent } from './organizations/manage/user-add-edit.component';
import { UserEventsComponent as OrgUserEventsComponent } from './organizations/manage/user-events.component';
import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/manage/user-groups.component';
import { ExportComponent as OrgExportComponent } from './organizations/tools/export.component';
@ -190,6 +191,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
OrgPeopleComponent,
OrgToolsComponent,
OrgUserAddEditComponent,
OrgUserEventsComponent,
OrgUserGroupsComponent,
OrganizationsComponent,
OrganizationLayoutComponent,
@ -242,6 +244,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
OrgEntityUsersComponent,
OrgGroupAddEditComponent,
OrgUserAddEditComponent,
OrgUserEventsComponent,
OrgUserGroupsComponent,
PasswordGeneratorHistoryComponent,
PurgeVaultComponent,

View File

@ -17,7 +17,7 @@
</div>
<i class="fa fa-spinner fa-spin text-muted" *ngIf="!loaded"></i>
<ng-container *ngIf="loaded">
<p *ngIf="!events || !events.length">{{'noItemsInList' | i18n}}</p>
<p *ngIf="!events || !events.length">{{'noEventsInList' | i18n}}</p>
<table class="table table-hover" *ngIf="events && events.length">
<thead>
<tr>

View File

@ -58,11 +58,11 @@
<i class="fa fa-fw fa-check"></i>
{{'confirm' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="groups(u)">
<a class="dropdown-item" href="#" appStopClick (click)="groups(u)" *ngIf="accessGroups">
<i class="fa fa-fw fa-sitemap"></i>
{{'groups' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="events(u)">
<a class="dropdown-item" href="#" appStopClick (click)="events(u)" *ngIf="accessEvents && u.status === organizationUserStatusType.Confirmed">
<i class="fa fa-fw fa-file-text-o"></i>
{{'eventLogs' | i18n}}
</a>
@ -79,3 +79,4 @@
</ng-container>
<ng-template #addEdit></ng-template>
<ng-template #groupsTemplate></ng-template>
<ng-template #eventsTemplate></ng-template>

View File

@ -14,6 +14,7 @@ import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
import { OrganizationUserConfirmRequest } from 'jslib/models/request/organizationUserConfirmRequest';
@ -26,6 +27,7 @@ import { Utils } from 'jslib/misc/utils';
import { ModalComponent } from '../../modal.component';
import { UserAddEditComponent } from './user-add-edit.component';
import { UserEventsComponent } from './user-events.component';
import { UserGroupsComponent } from './user-groups.component';
@Component({
@ -35,6 +37,7 @@ import { UserGroupsComponent } from './user-groups.component';
export class PeopleComponent implements OnInit {
@ViewChild('addEdit', { read: ViewContainerRef }) addEditModalRef: ViewContainerRef;
@ViewChild('groupsTemplate', { read: ViewContainerRef }) groupsModalRef: ViewContainerRef;
@ViewChild('eventsTemplate', { read: ViewContainerRef }) eventsModalRef: ViewContainerRef;
loading = true;
organizationId: string;
@ -43,17 +46,23 @@ export class PeopleComponent implements OnInit {
organizationUserType = OrganizationUserType;
organizationUserStatusType = OrganizationUserStatusType;
actionPromise: Promise<any>;
accessEvents = false;
accessGroups = false;
private modal: ModalComponent = null;
constructor(private apiService: ApiService, private route: ActivatedRoute,
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private platformUtilsService: PlatformUtilsService, private analytics: Angulartics2,
private toasterService: ToasterService, private cryptoService: CryptoService) { }
private toasterService: ToasterService, private cryptoService: CryptoService,
private userService: UserService) { }
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
const organization = await this.userService.getOrganization(this.organizationId);
this.accessEvents = organization.useEvents;
this.accessGroups = organization.useGroups;
await this.load();
});
}
@ -159,7 +168,22 @@ export class PeopleComponent implements OnInit {
}
async events(user: OrganizationUserUserDetailsResponse) {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.eventsModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<UserEventsComponent>(
UserEventsComponent, this.eventsModalRef);
childComponent.name = user != null ? user.name || user.email : null;
childComponent.organizationId = this.organizationId;
childComponent.organizationUserId = user != null ? user.id : null;
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
private async doConfirmation(user: OrganizationUserUserDetailsResponse) {

View File

@ -0,0 +1,68 @@
<div class="modal fade">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">
{{'eventLogs' | i18n}}
<small class="text-muted" *ngIf="name">{{name}}</small>
</h2>
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" *ngIf="!loaded">
<i class="fa fa-spinner fa-spin text-muted"></i>
</div>
<div class="modal-body" *ngIf="loaded">
<div class="d-flex">
<div class="form-inline">
<label class="sr-only" for="start">{{'startDate' | i18n}}</label>
<input type="datetime-local" class="form-control form-control-sm" id="start" placeholder="{{'startDate' | i18n}}" [(ngModel)]="start"
placeholder="YYYY-MM-DDTHH:MM">
<span class="mx-2">-</span>
<label class="sr-only" for="end">{{'endDate' | i18n}}</label>
<input type="datetime-local" class="form-control form-control-sm" id="end" placeholder="{{'endDate' | i18n}}" [(ngModel)]="end"
placeholder="YYYY-MM-DDTHH:MM">
</div>
<button #refreshBtn [appApiAction]="refreshPromise" type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="loadEvents(true)"
[disabled]="loaded && refreshBtn.loading">
<i class="fa fa-refresh fa-fw" [ngClass]="{'fa-spin': loaded && refreshBtn.loading}"></i>
{{'refresh' | i18n}}
</button>
</div>
<hr>
<div *ngIf="!events || !events.length">
{{'noEventsInList' | i18n}}
</div>
<table class="table table-hover mb-0" *ngIf="events && events.length">
<thead>
<tr>
<th class="border-top-0" width="210">{{'timestamp' | i18n}}</th>
<th class="border-top-0" width="40">
<span class="sr-only">{{'device' | i18n}}</span>
</th>
<th class="border-top-0">{{'event' | i18n}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let e of events">
<td>{{e.date | date:'medium'}}</td>
<td>
<i class="text-muted fa fa-lg {{e.appIcon}}" title="{{e.appName}}, {{e.ip}}"></i>
</td>
<td [innerHTML]="e.message"></td>
</tr>
</tbody>
</table>
<button #moreBtn [appApiAction]="morePromise" type="button" class="btn btn-block btn-link btn-submit" (click)="loadEvents(false)"
[disabled]="loaded && moreBtn.loading" *ngIf="continuationToken">
<i class="fa fa-spinner fa-spin"></i>
<span>{{'loadMore' | i18n}}</span>
</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,98 @@
import {
Component,
Input,
OnInit,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { EventService } from '../../services/event.service';
import { EventResponse } from 'jslib/models/response/eventResponse';
import { ListResponse } from 'jslib/models/response/listResponse';
@Component({
selector: 'app-user-events',
templateUrl: 'user-events.component.html',
})
export class UserEventsComponent implements OnInit {
@Input() name: string;
@Input() organizationUserId: string;
@Input() organizationId: string;
loading = true;
loaded = false;
events: any[];
start: string;
end: string;
continuationToken: string;
refreshPromise: Promise<any>;
morePromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private eventService: EventService, private toasterService: ToasterService) { }
async ngOnInit() {
const defaultDates = this.eventService.getDefaultDateFilters();
this.start = defaultDates[0];
this.end = defaultDates[1];
await this.loadEvents(true);
this.loaded = true;
}
async loadEvents(clearExisting: boolean) {
if (this.refreshPromise != null || this.morePromise != null) {
return;
}
let dates: string[] = null;
try {
dates = this.eventService.formatDateFilters(this.start, this.end);
} catch (e) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidDateRange'));
return;
}
this.loading = true;
let response: ListResponse<EventResponse>;
try {
const promise = this.apiService.getEventsOrganizationUser(this.organizationId, this.organizationUserId,
dates[0], dates[1], clearExisting ? null : this.continuationToken);
if (clearExisting) {
this.refreshPromise = promise;
} else {
this.morePromise = promise;
}
response = await promise;
} catch { }
this.continuationToken = response.continuationToken;
const events = response.data.map((r) => {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = this.eventService.getEventInfo(r);
return {
message: eventInfo.message,
appIcon: eventInfo.appIcon,
appName: eventInfo.appName,
userId: userId,
date: r.date,
ip: r.ipAddress,
type: r.type,
};
});
if (!clearExisting && this.events != null && this.events.length > 0) {
this.events = this.events.concat(events);
} else {
this.events = events;
}
this.loading = false;
this.morePromise = null;
this.refreshPromise = null;
}
}

View File

@ -600,6 +600,9 @@
"noUsersInList": {
"message": "There are no users to list."
},
"noEventsInList": {
"message": "There are no events to list."
},
"newOrganization": {
"message": "New Organization"
},