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?"
},