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 @@
- | {{'timestamp' | i18n}} |
- {{'user' | i18n}} |
+ {{'timestamp' | i18n}} |
+
+ {{'device' | i18n}}
+ |
+ {{'user' | i18n}} |
{{'event' | i18n}} |
@@ -30,11 +33,12 @@
| {{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"
}
}