mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-23 11:56:00 +01:00
user events
This commit is contained in:
parent
1f7ca7386a
commit
6d225beb46
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
68
src/app/organizations/manage/user-events.component.html
Normal file
68
src/app/organizations/manage/user-events.component.html
Normal 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">×</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>
|
98
src/app/organizations/manage/user-events.component.ts
Normal file
98
src/app/organizations/manage/user-events.component.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -600,6 +600,9 @@
|
||||
"noUsersInList": {
|
||||
"message": "There are no users to list."
|
||||
},
|
||||
"noEventsInList": {
|
||||
"message": "There are no events to list."
|
||||
},
|
||||
"newOrganization": {
|
||||
"message": "New Organization"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user