From b090de0da15e736c15a62dcd314cfceabb56af0e Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 9 Jul 2018 11:47:57 -0400 Subject: [PATCH] event info --- .../manage/events.component.html | 12 +- .../organizations/manage/events.component.ts | 48 ++-- src/app/services/event.service.ts | 219 ++++++++++++++++++ src/locales/en/messages.json | 173 ++++++++++++++ 4 files changed, 427 insertions(+), 25 deletions(-) diff --git a/src/app/organizations/manage/events.component.html b/src/app/organizations/manage/events.component.html index 52471cf4ffe..28c0a38b775 100644 --- a/src/app/organizations/manage/events.component.html +++ b/src/app/organizations/manage/events.component.html @@ -21,8 +21,11 @@ - - + + + @@ -30,11 +33,12 @@ +
{{'timestamp' | i18n}}{{'user' | i18n}}{{'timestamp' | i18n}} + {{'device' | i18n}} + {{'user' | i18n}} {{'event' | i18n}}
{{e.date | date:'medium'}} - {{e.userId}} + - {{e.type}} + {{e.userName}}
diff --git a/src/app/organizations/manage/events.component.ts b/src/app/organizations/manage/events.component.ts index 6f43650d079..dc2bb67479c 100644 --- a/src/app/organizations/manage/events.component.ts +++ b/src/app/organizations/manage/events.component.ts @@ -8,6 +8,9 @@ import { ApiService } from 'jslib/abstractions/api.service'; import { EventService } from '../../services/event.service'; +import { EventResponse } from 'jslib/models/response/eventResponse'; +import { ListResponse } from 'jslib/models/response/listResponse'; + @Component({ selector: 'app-org-events', templateUrl: 'events.component.html', @@ -56,6 +59,7 @@ export class EventsComponent implements OnInit { } this.loading = true; + let response: ListResponse; try { const promise = this.apiService.getEventsOrganization(this.organizationId, dates[0], dates[1], clearExisting ? null : this.continuationToken); @@ -64,28 +68,30 @@ export class EventsComponent implements OnInit { } else { this.morePromise = promise; } - const response = await promise; - this.continuationToken = response.continuationToken; - const events = response.data.map((r) => { - const userId = r.actingUserId == null ? r.userId : r.actingUserId; - const eventInfo: any = {}; - const htmlMessage = ''; - return { - message: htmlMessage, - appIcon: eventInfo.appIcon, - appName: eventInfo.appName, - userId: userId, - userName: userId != null ? 'user' : '-', - date: r.date, - ip: r.ipAddress, - }; - }); - if (!clearExisting && this.events != null && this.events.length > 0) { - this.events = this.events.concat(events); - } else { - this.events = events; - } + 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, + userName: userId != null ? 'user' : '-', + date: r.date, + ip: r.ipAddress, + }; + }); + + 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; diff --git a/src/app/services/event.service.ts b/src/app/services/event.service.ts index 505815b5c12..fade994c68c 100644 --- a/src/app/services/event.service.ts +++ b/src/app/services/event.service.ts @@ -2,6 +2,11 @@ import { Injectable } from '@angular/core'; import { I18nService } from 'jslib/abstractions/i18n.service'; +import { DeviceType } from 'jslib/enums/deviceType'; +import { EventType } from 'jslib/enums/eventType'; + +import { EventResponse } from 'jslib/models/response/eventResponse'; + @Injectable() export class EventService { constructor(private i18nService: I18nService) { } @@ -23,6 +28,210 @@ export class EventService { return [start.toISOString(), end.toISOString()]; } + getEventInfo(ev: EventResponse, options = new EventOptions()): EventInfo { + const appInfo = this.getAppInfo(ev.deviceType); + return { + message: this.getEventMessage(ev, options), + appIcon: appInfo[0], + appName: appInfo[1], + }; + } + + private getEventMessage(ev: EventResponse, options: EventOptions) { + let msg = ''; + switch (ev.type) { + // User + case EventType.User_LoggedIn: + msg = this.i18nService.t('loggedIn'); + break; + case EventType.User_ChangedPassword: + msg = this.i18nService.t('changedPassword'); + break; + case EventType.User_Enabled2fa: + msg = this.i18nService.t('enabled2fa'); + break; + case EventType.User_Disabled2fa: + msg = this.i18nService.t('disabled2fa'); + break; + case EventType.User_Recovered2fa: + msg = this.i18nService.t('recovered2fa'); + break; + case EventType.User_FailedLogIn: + msg = this.i18nService.t('failedLogin'); + break; + case EventType.User_FailedLogIn2fa: + msg = this.i18nService.t('failedLogin2fa'); + break; + // Cipher + case EventType.Cipher_Created: + msg = this.i18nService.t('createdThing', this.i18nService.t('item').toLocaleLowerCase(), + this.formatCipherId(ev, options)); + break; + case EventType.Cipher_Updated: + msg = this.i18nService.t('editedThing', this.i18nService.t('item').toLocaleLowerCase(), + this.formatCipherId(ev, options)); + break; + case EventType.Cipher_Deleted: + msg = this.i18nService.t('deletedThing', this.i18nService.t('item').toLocaleLowerCase(), + this.formatCipherId(ev, options)); + break; + case EventType.Cipher_AttachmentCreated: + msg = this.i18nService.t('createdAttachmentForItem', this.formatCipherId(ev, options)); + break; + case EventType.Cipher_AttachmentDeleted: + msg = this.i18nService.t('deletedAttachmentForItem', this.formatCipherId(ev, options)); + break; + case EventType.Cipher_Shared: + msg = this.i18nService.t('sharedThing', this.i18nService.t('item').toLocaleLowerCase(), + this.formatCipherId(ev, options)); + break; + case EventType.Cipher_UpdatedCollections: + msg = this.i18nService.t('editedCollectionsForItem', this.formatCipherId(ev, options)); + break; + // Collection + case EventType.Collection_Created: + msg = this.i18nService.t('createdThing', this.i18nService.t('collection').toLocaleLowerCase(), + this.formatCollectionId(ev)); + break; + case EventType.Collection_Updated: + msg = this.i18nService.t('editedThing', this.i18nService.t('collection').toLocaleLowerCase(), + this.formatCollectionId(ev)); + break; + case EventType.Collection_Deleted: + msg = this.i18nService.t('deletedThing', this.i18nService.t('collection').toLocaleLowerCase(), + this.formatCollectionId(ev)); + break; + // Group + case EventType.Group_Created: + msg = this.i18nService.t('createdThing', this.i18nService.t('group').toLocaleLowerCase(), + this.formatGroupId(ev)); + break; + case EventType.Group_Updated: + msg = this.i18nService.t('editedThing', this.i18nService.t('group').toLocaleLowerCase(), + this.formatGroupId(ev)); + break; + case EventType.Group_Deleted: + msg = this.i18nService.t('deletedThing', this.i18nService.t('group').toLocaleLowerCase(), + this.formatGroupId(ev)); + break; + // Org user + case EventType.OrganizationUser_Invited: + msg = this.i18nService.t('invitedUser', this.formatOrgUserId(ev)); + break; + case EventType.OrganizationUser_Confirmed: + msg = this.i18nService.t('confirmedUser', this.formatOrgUserId(ev)); + break; + case EventType.OrganizationUser_Updated: + msg = this.i18nService.t('editedThing', this.i18nService.t('user').toLocaleLowerCase(), + this.formatOrgUserId(ev)); + break; + case EventType.OrganizationUser_Removed: + msg = this.i18nService.t('removedThing', this.i18nService.t('user').toLocaleLowerCase(), + this.formatOrgUserId(ev)); + break; + case EventType.OrganizationUser_UpdatedGroups: + msg = this.i18nService.t('editedGroupsForUser', this.formatOrgUserId(ev)); + break; + // Org + case EventType.Organization_Updated: + msg = this.i18nService.t('editedOrgSettings'); + break; + default: + break; + } + return msg === '' ? null : msg; + } + + private getAppInfo(deviceType: DeviceType): [string, string] { + switch (deviceType) { + case DeviceType.Android: + return ['fa-android', this.i18nService.t('mobile') + ' - Android']; + case DeviceType.iOS: + return ['fa-apple', this.i18nService.t('mobile') + ' - iOS']; + case DeviceType.UWP: + return ['fa-windows', this.i18nService.t('mobile') + ' - Windows']; + case DeviceType.ChromeExtension: + return ['fa-chrome', this.i18nService.t('extension') + ' - Chrome']; + case DeviceType.FirefoxExtension: + return ['fa-firefox', this.i18nService.t('extension') + ' - Firefox']; + case DeviceType.OperaExtension: + return ['fa-opera', this.i18nService.t('extension') + ' - Opera']; + case DeviceType.EdgeExtension: + return ['fa-edge', this.i18nService.t('extension') + ' - Edge']; + case DeviceType.VivaldiExtension: + return ['fa-puzzle-piece', this.i18nService.t('extension') + ' - Vivaldi']; + case DeviceType.SafariExtension: + return ['fa-safari', this.i18nService.t('extension') + ' - Safari']; + case DeviceType.WindowsDesktop: + return ['fa-windows', this.i18nService.t('desktop') + ' - Windows']; + case DeviceType.MacOsDesktop: + return ['fa-apple', this.i18nService.t('desktop') + ' - macOS']; + case DeviceType.LinuxDesktop: + return ['fa-linux', this.i18nService.t('desktop') + ' - Linux']; + case DeviceType.ChromeBrowser: + return ['fa-globe', this.i18nService.t('webVault') + ' - Chrome']; + case DeviceType.FirefoxBrowser: + return ['fa-globe', this.i18nService.t('webVault') + ' - Firefox']; + case DeviceType.OperaBrowser: + return ['fa-globe', this.i18nService.t('webVault') + ' - Opera']; + case DeviceType.SafariBrowser: + return ['fa-globe', this.i18nService.t('webVault') + ' - Safari']; + case DeviceType.VivaldiBrowser: + return ['fa-globe', this.i18nService.t('webVault') + ' - Vivaldi']; + case DeviceType.EdgeBrowser: + return ['fa-globe', this.i18nService.t('webVault') + ' - Edge']; + case DeviceType.IEBrowser: + return ['fa-globe', this.i18nService.t('webVault') + ' - IE']; + case DeviceType.UnknownBrowser: + return ['fa-globe', this.i18nService.t('webVault') + ' - ' + this.i18nService.t('unknown')]; + default: + return ['fa-globe', this.i18nService.t('unknown')]; + } + } + + private formatCipherId(ev: EventResponse, options: EventOptions) { + const shortId = this.getShortId(ev.cipherId); + if (ev.organizationId == null || !options.cipherInfo) { + return '' + shortId + ''; + } + const a = this.makeAnchor(shortId); + a.setAttribute('href', '#/organizations/' + ev.organizationId + '/vault?search=' + shortId + + '&viewEvents=' + ev.cipherId); + return a.outerHTML; + } + + private formatGroupId(ev: EventResponse) { + const shortId = this.getShortId(ev.groupId); + const a = this.makeAnchor(shortId); + a.setAttribute('href', '#/organizations/' + ev.organizationId + '/manage/groups?search=' + shortId); + return a.outerHTML; + } + + private formatCollectionId(ev: EventResponse) { + const shortId = this.getShortId(ev.collectionId); + const a = this.makeAnchor(shortId); + a.setAttribute('href', '#/organizations/' + ev.organizationId + '/manage/collections?search=' + shortId); + return a.outerHTML; + } + + private formatOrgUserId(ev: EventResponse) { + const shortId = this.getShortId(ev.organizationUserId); + const a = this.makeAnchor(shortId); + a.setAttribute('href', '#/organizations/' + ev.organizationId + '/manage/people?search=' + shortId); + return a.outerHTML; + } + + private makeAnchor(shortId: string) { + const a = document.createElement('a'); + a.title = this.i18nService.t('view'); + a.innerHTML = '' + shortId + ''; + return a; + } + + private getShortId(id: string) { + return id.substring(0, 8); + } + private toDateTimeLocalString(date: Date) { return date.getFullYear() + '-' + this.pad(date.getMonth() + 1) + @@ -36,3 +245,13 @@ export class EventService { return (norm < 10 ? '0' : '') + norm; } } + +export class EventInfo { + message: string; + appIcon: string; + appName: string; +} + +export class EventOptions { + cipherInfo = true; +} diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 5213c70b0b7..bde691570fd 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -1747,5 +1747,178 @@ }, "loadMore": { "message": "Load More" + }, + "mobile": { + "message": "Mobile", + "description": "Mobile app" + }, + "extension": { + "message": "Extension", + "description": "Browser extension/addon" + }, + "desktop": { + "message": "Desktop", + "description": "Desktop app" + }, + "webVault": { + "message": "Web Vault" + }, + "loggedIn": { + "message": "Logged in." + }, + "changedPassword": { + "message": "Changed account password." + }, + "enabled2fa": { + "message": "Enabled two-step login." + }, + "disabled2fa": { + "message": "Disabled two-step login." + }, + "recovered2fa": { + "message": "Recovered account from two-step login." + }, + "failedLogin": { + "message": "Login attempt failed with incorrect password." + }, + "failedLogin2fa": { + "message": "Login attempt failed with incorrect two-step login." + }, + "editedOrgSettings": { + "message": "Edited organization settings." + }, + "createdThing": { + "message": "Created $THING$ $ID$.", + "description": "Created item abe89f32.", + "placeholders": { + "thing": { + "content": "$1", + "example": "item" + }, + "id": { + "content": "$2", + "example": "abe89f32" + } + } + }, + "editedThing": { + "message": "Edited $THING$ $ID$.", + "description": "Edited item abe89f32.", + "placeholders": { + "thing": { + "content": "$1", + "example": "item" + }, + "id": { + "content": "$2", + "example": "abe89f32" + } + } + }, + "deletedThing": { + "message": "Deleted $THING$ $ID$.", + "description": "Deleted item abe89f32.", + "placeholders": { + "thing": { + "content": "$1", + "example": "item" + }, + "id": { + "content": "$2", + "example": "abe89f32" + } + } + }, + "sharedThing": { + "message": "Shared $THING$ $ID$.", + "placeholders": { + "thing": { + "content": "$1", + "example": "item" + }, + "id": { + "content": "$2", + "example": "abe89f32" + } + } + }, + "removedThing": { + "message": "Removed $THING$ $ID$.", + "placeholders": { + "thing": { + "content": "$1", + "example": "item" + }, + "id": { + "content": "$2", + "example": "abe89f32" + } + } + }, + "createdAttachmentForItem": { + "message": "Created attachment for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "abe89f32" + } + } + }, + "deletedAttachmentForItem": { + "message": "Deleted attachment for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "abe89f32" + } + } + }, + "editedCollectionsForItem": { + "message": "Edited collections for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "abe89f32" + } + } + }, + "invitedUser": { + "message": "Invited user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "abe89f32" + } + } + }, + "confirmedUser": { + "message": "Confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "abe89f32" + } + } + }, + "editedGroupsForUser": { + "message": "Edited groups for user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "abe89f32" + } + } + }, + "item": { + "message": "Item" + }, + "collection": { + "message": "Collection" + }, + "group": { + "message": "Group" + }, + "device": { + "message": "Device" } }