diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html index 336285b2a4..23e9c6df17 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -39,6 +39,11 @@ *ngIf="organization.canAccessReports" > + ; hideNewOrgButton$: Observable; organizationIsUnmanaged$: Observable; + isAccessIntelligenceFeatureEnabled = false; private _destroy = new Subject(); @@ -70,6 +71,10 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { async ngOnInit() { document.body.classList.remove("layout_frontend"); + this.isAccessIntelligenceFeatureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.AccessIntelligence, + ); + this.organization$ = this.route.params .pipe(takeUntil(this._destroy)) .pipe(map((p) => p.organizationId)) diff --git a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts index 538cc45ac6..a36b267e2f 100644 --- a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts @@ -62,6 +62,13 @@ const routes: Routes = [ (m) => m.OrganizationReportingModule, ), }, + { + path: "access-intelligence", + loadChildren: () => + import("../../tools/access-intelligence/access-intelligence.module").then( + (m) => m.AccessIntelligenceModule, + ), + }, { path: "billing", loadChildren: () => diff --git a/apps/web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts b/apps/web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts new file mode 100644 index 0000000000..b35b1fa64a --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { unauthGuardFn } from "@bitwarden/angular/auth/guards"; +import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; + +import { AccessIntelligenceComponent } from "./access-intelligence.component"; + +const routes: Routes = [ + { + path: "", + component: AccessIntelligenceComponent, + canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence), unauthGuardFn()], + data: { + titleId: "accessIntelligence", + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class AccessIntelligenceRoutingModule {} diff --git a/apps/web/src/app/tools/access-intelligence/access-intelligence.component.html b/apps/web/src/app/tools/access-intelligence/access-intelligence.component.html new file mode 100644 index 0000000000..665f8f6b0c --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/access-intelligence.component.html @@ -0,0 +1,23 @@ + + + +

{{ "allApplications" | i18n }}

+ +
+ + + + {{ "priorityApplicationsWithCount" | i18n: priorityApps.length }} + +

{{ "priorityApplications" | i18n }}

+ +
+ + + + {{ "notifiedMembersWithCount" | i18n: priorityApps.length }} + +

{{ "notifiedMembers" | i18n }}

+ +
+
diff --git a/apps/web/src/app/tools/access-intelligence/access-intelligence.component.ts b/apps/web/src/app/tools/access-intelligence/access-intelligence.component.ts new file mode 100644 index 0000000000..9e5eff6f62 --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/access-intelligence.component.ts @@ -0,0 +1,45 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute } from "@angular/router"; +import { first } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { TabsModule } from "@bitwarden/components"; + +import { HeaderModule } from "../../layouts/header/header.module"; + +import { ApplicationTableComponent } from "./application-table.component"; +import { NotifiedMembersTableComponent } from "./notified-members-table.component"; + +export enum AccessIntelligenceTabType { + AllApps = 0, + PriorityApps = 1, + NotifiedMembers = 2, +} + +@Component({ + standalone: true, + templateUrl: "./access-intelligence.component.html", + imports: [ + ApplicationTableComponent, + CommonModule, + JslibModule, + HeaderModule, + NotifiedMembersTableComponent, + TabsModule, + ], +}) +export class AccessIntelligenceComponent { + tabIndex: AccessIntelligenceTabType; + + apps: any[] = []; + priorityApps: any[] = []; + notifiedMembers: any[] = []; + + constructor(route: ActivatedRoute) { + route.queryParams.pipe(takeUntilDestroyed(), first()).subscribe(({ tabIndex }) => { + this.tabIndex = !isNaN(tabIndex) ? tabIndex : AccessIntelligenceTabType.AllApps; + }); + } +} diff --git a/apps/web/src/app/tools/access-intelligence/access-intelligence.module.ts b/apps/web/src/app/tools/access-intelligence/access-intelligence.module.ts new file mode 100644 index 0000000000..32b66935b6 --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/access-intelligence.module.ts @@ -0,0 +1,9 @@ +import { NgModule } from "@angular/core"; + +import { AccessIntelligenceRoutingModule } from "./access-intelligence-routing.module"; +import { AccessIntelligenceComponent } from "./access-intelligence.component"; + +@NgModule({ + imports: [AccessIntelligenceComponent, AccessIntelligenceRoutingModule], +}) +export class AccessIntelligenceModule {} diff --git a/apps/web/src/app/tools/access-intelligence/application-table.component.html b/apps/web/src/app/tools/access-intelligence/application-table.component.html new file mode 100644 index 0000000000..4986483cb7 --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/application-table.component.html @@ -0,0 +1,11 @@ + + + + {{ "application" | i18n }} + {{ "atRiskPasswords" | i18n }} + {{ "totalPasswords" | i18n }} + {{ "atRiskMembers" | i18n }} + {{ "totalMembers" | i18n }} + + + diff --git a/apps/web/src/app/tools/access-intelligence/application-table.component.ts b/apps/web/src/app/tools/access-intelligence/application-table.component.ts new file mode 100644 index 0000000000..79b8500b8c --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/application-table.component.ts @@ -0,0 +1,19 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { TableDataSource, TableModule } from "@bitwarden/components"; + +@Component({ + standalone: true, + selector: "tools-application-table", + templateUrl: "./application-table.component.html", + imports: [CommonModule, JslibModule, TableModule], +}) +export class ApplicationTableComponent { + protected dataSource = new TableDataSource(); + + constructor() { + this.dataSource.data = []; + } +} diff --git a/apps/web/src/app/tools/access-intelligence/notified-members-table.component.html b/apps/web/src/app/tools/access-intelligence/notified-members-table.component.html new file mode 100644 index 0000000000..dc94f28f94 --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/notified-members-table.component.html @@ -0,0 +1,11 @@ + + + + {{ "member" | i18n }} + {{ "atRiskPasswords" | i18n }} + {{ "totalPasswords" | i18n }} + {{ "atRiskApplications" | i18n }} + {{ "totalApplications" | i18n }} + + + diff --git a/apps/web/src/app/tools/access-intelligence/notified-members-table.component.ts b/apps/web/src/app/tools/access-intelligence/notified-members-table.component.ts new file mode 100644 index 0000000000..d50436061c --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/notified-members-table.component.ts @@ -0,0 +1,19 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { TableDataSource, TableModule } from "@bitwarden/components"; + +@Component({ + standalone: true, + selector: "tools-notified-members-table", + templateUrl: "./notified-members-table.component.html", + imports: [CommonModule, JslibModule, TableModule], +}) +export class NotifiedMembersTableComponent { + dataSource = new TableDataSource(); + + constructor() { + this.dataSource.data = []; + } +} diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 96afa9dd1a..6d2196466f 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1,4 +1,64 @@ { + "allApplications": { + "message": "All applications" + }, + "priorityApplications": { + "message": "Priority applications" + }, + "accessIntelligence": { + "message": "Access Intelligence" + }, + "notifiedMembers": { + "message": "Notified members" + }, + "allApplicationsWithCount": { + "message": "All applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "priorityApplicationsWithCount": { + "message": "Priority applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "notifiedMembersWithCount": { + "message": "Notified members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "application": { + "message": "Application" + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "totalPasswords": { + "message": "Total passwords" + }, + "atRiskMembers": { + "message": "At-risk members" + }, + "totalMembers": { + "message": "Total members" + }, + "atRiskApplications": { + "message": "At-risk applications" + }, + "totalApplications": { + "message": "Total applications" + }, "whatTypeOfItem": { "message": "What type of item is this?" },