mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-26031] Drawer Service State Refactoring (#16580)
* refactor(risk-insights-data.service): unify drawer state management with BehaviorSubject - Replace individual drawer properties with unified drawerDetailsSubject - Add reactive Observable getters for drawer state checking - Update all drawer methods to use centralized state management * risk-insights.component: add special case drawer state sync in component - Add private _isDrawerOpen property for internal state tracking - Subscribe to drawerDetails$ changes with takeUntilDestroyed cleanup - Implement getter/setter for isDrawerOpen to sync component <-> service - Enable two-way binding while maintaining reactive patterns * risk-insights.component.html: replace drawer template with unified observable patterns - Replace dataService.openDrawer with isDrawerOpen special case getter - Wrap drawer in @if block with drawerDetails$ | async for single subscription - Update isActiveDrawerType() calls to reactive isActiveDrawerType$() | async - Replace direct property access with unified drawerDetails object - Use modern @if control flow syntax for better performance * all-applications.component.html: replace drawer state with reactive observable patterns - Replace dataService.drawerInvokerId with drawerDetails$ | async in card highlighting - Update app-table-row-scrollable input from isDrawerIsOpenForThisRecord function to openApplication string * critical-applications.component.html: replace drawer state with reactive observable patterns - Replace dataService.drawerInvokerId with drawerDetails$ | async in card highlighting - Update table component binding from isDrawerIsOpenForThisRecord to openApplication - Use reactive drawer state checking for consistent behavior with all-applications * all-applications.component.ts: remove deprecated drawer state functions - Remove unused trackByFunction that's no longer needed in template - Remove getSelectedUrls function that's not used anywhere - Remove isDrawerOpenForTableRow replaced by reactive openApplication binding - Clean up unused ApplicationHealthReportDetail import - Simplifies component interface following reactive pattern migration * critical-applications.component.ts: remove deprecated drawer state functions - Remove unused trackByFunction that's no longer needed in template - Remove isDrawerOpenForTableRow replaced by reactive openApplication binding * app-table-row-scrollable.component.html: replace drawer function calls with string comparison - Replace isDrawerIsOpenForThisRecord(row.applicationName) with row.applicationName === openApplication - Use direct string comparison instead of function calls for better performance - Matches updated component input from function to string property - Simplifies template logic following reactive pattern migration * fix(risk-insights-data.service.ts): restore drawer toggle behavior in setter methods - Add toggle logic to check if same drawer type and invoker are already open - Close drawer when clicking same button twice (preserves original UX) - Switch drawer content when clicking different button - Maintains reactive patterns while restoring expected behavior * revert to drawer state functions to maintain scope of task - the logic replacing these functions will be in pr16523 * fix(risk-insights-data.service.ts): restore boolean isActiveDrawerType function per review feedback - Keep original isActiveDrawerType() as boolean function using drawerDetailsSubject.value - Maintain isActiveDrawerType$() as Observable version for reactive templates - Apply same pattern to isDrawerOpenForInvoker() for consistency - Addresses review feedback to preserve existing function signatures * refactor(risk-insights-data.service.ts): use destructuring in drawer setter methods per review feedback * refactor(all-applications.component.html): optimize single subscription for drawer state per review feedback * refactor(critical-applications.component.html): optimize single subscription for drawer state per review feedback * refactor(risk-insights.component.html): use boolean drawer type functions per review feedback * fix(browser-system-notification.service.ts): restore eslint disable comment removed by prettier --------- Co-authored-by: Tom <144813356+ttalty@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs";
|
||||
import { finalize, switchMap, withLatestFrom } from "rxjs/operators";
|
||||
import { finalize, switchMap, withLatestFrom, map } from "rxjs/operators";
|
||||
|
||||
import {
|
||||
getOrganizationById,
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
AtRiskApplicationDetail,
|
||||
AtRiskMemberDetail,
|
||||
DrawerType,
|
||||
DrawerDetails,
|
||||
ApplicationHealthReportDetail,
|
||||
ApplicationHealthReportDetailEnriched,
|
||||
} from "../models/report-models";
|
||||
@@ -53,12 +54,17 @@ export class RiskInsightsDataService {
|
||||
private errorSubject = new BehaviorSubject<string | null>(null);
|
||||
error$ = this.errorSubject.asObservable();
|
||||
|
||||
openDrawer = false;
|
||||
drawerInvokerId: string = "";
|
||||
activeDrawerType: DrawerType = DrawerType.None;
|
||||
atRiskMemberDetails: AtRiskMemberDetail[] = [];
|
||||
appAtRiskMembers: AppAtRiskMembersDialogParams | null = null;
|
||||
atRiskAppDetails: AtRiskApplicationDetail[] | null = null;
|
||||
// ------------------------- Drawer Variables ----------------
|
||||
// Drawer variables unified into a single BehaviorSubject
|
||||
private drawerDetailsSubject = new BehaviorSubject<DrawerDetails>({
|
||||
open: false,
|
||||
invokerId: "",
|
||||
activeDrawerType: DrawerType.None,
|
||||
atRiskMemberDetails: [],
|
||||
appAtRiskMembers: null,
|
||||
atRiskAppDetails: null,
|
||||
});
|
||||
drawerDetails$ = this.drawerDetailsSubject.asObservable();
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
@@ -178,56 +184,96 @@ export class RiskInsightsDataService {
|
||||
}
|
||||
|
||||
// ------------------------------- Drawer management methods -------------------------------
|
||||
// ------------------------- Drawer functions -----------------------------
|
||||
|
||||
isActiveDrawerType$ = (drawerType: DrawerType): Observable<boolean> => {
|
||||
return this.drawerDetails$.pipe(map((details) => details.activeDrawerType === drawerType));
|
||||
};
|
||||
isActiveDrawerType = (drawerType: DrawerType): boolean => {
|
||||
return this.activeDrawerType === drawerType;
|
||||
return this.drawerDetailsSubject.value.activeDrawerType === drawerType;
|
||||
};
|
||||
|
||||
isDrawerOpenForInvoker$ = (applicationName: string) => {
|
||||
return this.drawerDetails$.pipe(map((details) => details.invokerId === applicationName));
|
||||
};
|
||||
isDrawerOpenForInvoker = (applicationName: string): boolean => {
|
||||
return this.drawerDetailsSubject.value.invokerId === applicationName;
|
||||
};
|
||||
|
||||
closeDrawer = (): void => {
|
||||
this.drawerDetailsSubject.next({
|
||||
open: false,
|
||||
invokerId: "",
|
||||
activeDrawerType: DrawerType.None,
|
||||
atRiskMemberDetails: [],
|
||||
appAtRiskMembers: null,
|
||||
atRiskAppDetails: null,
|
||||
});
|
||||
};
|
||||
|
||||
setDrawerForOrgAtRiskMembers = (
|
||||
atRiskMemberDetails: AtRiskMemberDetail[],
|
||||
invokerId: string = "",
|
||||
): void => {
|
||||
this.resetDrawer(DrawerType.OrgAtRiskMembers);
|
||||
this.activeDrawerType = DrawerType.OrgAtRiskMembers;
|
||||
this.drawerInvokerId = invokerId;
|
||||
this.atRiskMemberDetails = atRiskMemberDetails;
|
||||
this.openDrawer = !this.openDrawer;
|
||||
const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value;
|
||||
const shouldClose =
|
||||
open && activeDrawerType === DrawerType.OrgAtRiskMembers && currentInvokerId === invokerId;
|
||||
|
||||
if (shouldClose) {
|
||||
this.closeDrawer();
|
||||
} else {
|
||||
this.drawerDetailsSubject.next({
|
||||
open: true,
|
||||
invokerId,
|
||||
activeDrawerType: DrawerType.OrgAtRiskMembers,
|
||||
atRiskMemberDetails,
|
||||
appAtRiskMembers: null,
|
||||
atRiskAppDetails: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setDrawerForAppAtRiskMembers = (
|
||||
atRiskMembersDialogParams: AppAtRiskMembersDialogParams,
|
||||
invokerId: string = "",
|
||||
): void => {
|
||||
this.resetDrawer(DrawerType.None);
|
||||
this.activeDrawerType = DrawerType.AppAtRiskMembers;
|
||||
this.drawerInvokerId = invokerId;
|
||||
this.appAtRiskMembers = atRiskMembersDialogParams;
|
||||
this.openDrawer = !this.openDrawer;
|
||||
const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value;
|
||||
const shouldClose =
|
||||
open && activeDrawerType === DrawerType.AppAtRiskMembers && currentInvokerId === invokerId;
|
||||
|
||||
if (shouldClose) {
|
||||
this.closeDrawer();
|
||||
} else {
|
||||
this.drawerDetailsSubject.next({
|
||||
open: true,
|
||||
invokerId,
|
||||
activeDrawerType: DrawerType.AppAtRiskMembers,
|
||||
atRiskMemberDetails: [],
|
||||
appAtRiskMembers: atRiskMembersDialogParams,
|
||||
atRiskAppDetails: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setDrawerForOrgAtRiskApps = (
|
||||
atRiskApps: AtRiskApplicationDetail[],
|
||||
invokerId: string = "",
|
||||
): void => {
|
||||
this.resetDrawer(DrawerType.OrgAtRiskApps);
|
||||
this.activeDrawerType = DrawerType.OrgAtRiskApps;
|
||||
this.drawerInvokerId = invokerId;
|
||||
this.atRiskAppDetails = atRiskApps;
|
||||
this.openDrawer = !this.openDrawer;
|
||||
};
|
||||
const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value;
|
||||
const shouldClose =
|
||||
open && activeDrawerType === DrawerType.OrgAtRiskApps && currentInvokerId === invokerId;
|
||||
|
||||
closeDrawer = (): void => {
|
||||
this.resetDrawer(DrawerType.None);
|
||||
};
|
||||
|
||||
private resetDrawer = (drawerType: DrawerType): void => {
|
||||
if (this.activeDrawerType !== drawerType) {
|
||||
this.openDrawer = false;
|
||||
if (shouldClose) {
|
||||
this.closeDrawer();
|
||||
} else {
|
||||
this.drawerDetailsSubject.next({
|
||||
open: true,
|
||||
invokerId,
|
||||
activeDrawerType: DrawerType.OrgAtRiskApps,
|
||||
atRiskMemberDetails: [],
|
||||
appAtRiskMembers: null,
|
||||
atRiskAppDetails: atRiskApps,
|
||||
});
|
||||
}
|
||||
|
||||
this.activeDrawerType = DrawerType.None;
|
||||
this.atRiskMemberDetails = [];
|
||||
this.appAtRiskMembers = null;
|
||||
this.atRiskAppDetails = null;
|
||||
this.drawerInvokerId = "";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,44 +25,46 @@
|
||||
</div>
|
||||
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!(isLoading$ | async) && dataSource.data.length">
|
||||
<h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2>
|
||||
<div class="tw-flex tw-gap-6">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskMembers('allAppsOrgAtRiskMembers')"
|
||||
>
|
||||
<dirt-card
|
||||
#allAppsOrgAtRiskMembers
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': dataService.drawerInvokerId === 'allAppsOrgAtRiskMembers',
|
||||
}"
|
||||
[title]="'atRiskMembers' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskMemberCount"
|
||||
[maxValue]="applicationSummary.totalMemberCount"
|
||||
@if (dataService.drawerDetails$ | async; as drawerDetails) {
|
||||
<div class="tw-flex tw-gap-6">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskMembers('allAppsOrgAtRiskMembers')"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskApps('allAppsOrgAtRiskApplications')"
|
||||
>
|
||||
<dirt-card
|
||||
#allAppsOrgAtRiskApplications
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': dataService.drawerInvokerId === 'allAppsOrgAtRiskApplications',
|
||||
}"
|
||||
[title]="'atRiskApplications' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskApplicationCount"
|
||||
[maxValue]="applicationSummary.totalApplicationCount"
|
||||
<dirt-card
|
||||
#allAppsOrgAtRiskMembers
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskMembers',
|
||||
}"
|
||||
[title]="'atRiskMembers' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskMemberCount"
|
||||
[maxValue]="applicationSummary.totalMemberCount"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskApps('allAppsOrgAtRiskApplications')"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
</div>
|
||||
<dirt-card
|
||||
#allAppsOrgAtRiskApplications
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskApplications',
|
||||
}"
|
||||
[title]="'atRiskApplications' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskApplicationCount"
|
||||
[maxValue]="applicationSummary.totalApplicationCount"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
|
||||
<bit-search
|
||||
[placeholder]="'searchApps' | i18n"
|
||||
|
||||
@@ -14,10 +14,7 @@ import {
|
||||
LEGACY_ApplicationHealthReportDetailWithCriticalFlag,
|
||||
LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
|
||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health";
|
||||
import {
|
||||
ApplicationHealthReportDetail,
|
||||
OrganizationReportSummary,
|
||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
|
||||
import { OrganizationReportSummary } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
|
||||
import { RiskInsightsEncryptionService } from "@bitwarden/bit-common/dirt/reports/risk-insights/services/risk-insights-encryption.service";
|
||||
import {
|
||||
getOrganizationById,
|
||||
@@ -193,10 +190,6 @@ export class AllApplicationsComponent implements OnInit {
|
||||
}
|
||||
};
|
||||
|
||||
trackByFunction(_: number, item: ApplicationHealthReportDetail) {
|
||||
return item.applicationName;
|
||||
}
|
||||
|
||||
showAppAtRiskMembers = async (applicationName: string) => {
|
||||
const info = {
|
||||
members:
|
||||
@@ -226,9 +219,9 @@ export class AllApplicationsComponent implements OnInit {
|
||||
}
|
||||
};
|
||||
|
||||
getSelectedUrls = () => Array.from(this.selectedUrls);
|
||||
|
||||
isDrawerOpenForTableRow = (applicationName: string): boolean => {
|
||||
return this.dataService.drawerInvokerId === applicationName;
|
||||
// Note: This function will be replaced by PR #16523 with openApplication binding
|
||||
// Using private access to BehaviorSubject value for backward compatibility
|
||||
return (this.dataService as any).drawerDetailsSubject?.value?.invokerId === applicationName;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,44 +43,46 @@
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="tw-flex tw-gap-6">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskMembers('criticalAppsAtRiskMembers')"
|
||||
>
|
||||
<dirt-card
|
||||
#criticalAppsAtRiskMembers
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': dataService.drawerInvokerId === 'criticalAppsAtRiskMembers',
|
||||
}"
|
||||
[title]="'atRiskMembers' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskMemberCount"
|
||||
[maxValue]="applicationSummary.totalMemberCount"
|
||||
@if (dataService.drawerDetails$ | async; as drawerDetails) {
|
||||
<div class="tw-flex tw-gap-6">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskMembers('criticalAppsAtRiskMembers')"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskApps('criticalAppsAtRiskApplications')"
|
||||
>
|
||||
<dirt-card
|
||||
#criticalAppsAtRiskApplications
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': dataService.drawerInvokerId === 'criticalAppsAtRiskApplications',
|
||||
}"
|
||||
[title]="'atRiskApplications' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskApplicationCount"
|
||||
[maxValue]="applicationSummary.totalApplicationCount"
|
||||
<dirt-card
|
||||
#criticalAppsAtRiskMembers
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': drawerDetails.invokerId === 'criticalAppsAtRiskMembers',
|
||||
}"
|
||||
[title]="'atRiskMembers' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskMemberCount"
|
||||
[maxValue]="applicationSummary.totalMemberCount"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tw-flex-1"
|
||||
tabindex="0"
|
||||
(click)="showOrgAtRiskApps('criticalAppsAtRiskApplications')"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
</div>
|
||||
<dirt-card
|
||||
#criticalAppsAtRiskApplications
|
||||
class="tw-w-full"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-100': drawerDetails.invokerId === 'criticalAppsAtRiskApplications',
|
||||
}"
|
||||
[title]="'atRiskApplications' | i18n"
|
||||
[value]="applicationSummary.totalAtRiskApplicationCount"
|
||||
[maxValue]="applicationSummary.totalApplicationCount"
|
||||
>
|
||||
</dirt-card>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
|
||||
<bit-search
|
||||
[placeholder]="'searchApps' | i18n"
|
||||
|
||||
@@ -202,10 +202,9 @@ export class CriticalApplicationsComponent implements OnInit {
|
||||
this.dataService.setDrawerForOrgAtRiskApps(data, invokerId);
|
||||
};
|
||||
|
||||
trackByFunction(_: number, item: LEGACY_ApplicationHealthReportDetailWithCriticalFlag) {
|
||||
return item.applicationName;
|
||||
}
|
||||
isDrawerOpenForTableRow = (applicationName: string) => {
|
||||
return this.dataService.drawerInvokerId === applicationName;
|
||||
// Note: This function will be replaced by PR #16523 with openApplication binding
|
||||
// Using private access to BehaviorSubject value for backward compatibility
|
||||
return (this.dataService as any).drawerDetailsSubject?.value?.invokerId === applicationName;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,93 +53,91 @@
|
||||
</bit-tab>
|
||||
</bit-tab-group>
|
||||
|
||||
<bit-drawer
|
||||
style="width: 30%"
|
||||
[(open)]="dataService.openDrawer"
|
||||
(openChange)="dataService.closeDrawer()"
|
||||
>
|
||||
<ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.OrgAtRiskMembers)">
|
||||
<bit-drawer-header
|
||||
title="{{ 'atRiskMembersWithCount' | i18n: dataService.atRiskMemberDetails.length }}"
|
||||
>
|
||||
</bit-drawer-header>
|
||||
<bit-drawer-body>
|
||||
<span bitTypography="body1" class="tw-text-muted tw-text-sm">{{
|
||||
(dataService.atRiskMemberDetails.length > 0
|
||||
? "atRiskMembersDescription"
|
||||
: "atRiskMembersDescriptionNone"
|
||||
) | i18n
|
||||
}}</span>
|
||||
<ng-container *ngIf="dataService.atRiskMemberDetails.length > 0">
|
||||
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">{{ "email" | i18n }}</div>
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">
|
||||
{{ "atRiskPasswords" | i18n }}
|
||||
@if (dataService.drawerDetails$ | async; as drawerDetails) {
|
||||
<bit-drawer style="width: 30%" [(open)]="isDrawerOpen" (openChange)="dataService.closeDrawer()">
|
||||
<ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.OrgAtRiskMembers)">
|
||||
<bit-drawer-header
|
||||
title="{{ 'atRiskMembersWithCount' | i18n: drawerDetails.atRiskMemberDetails.length }}"
|
||||
>
|
||||
</bit-drawer-header>
|
||||
<bit-drawer-body>
|
||||
<span bitTypography="body1" class="tw-text-muted tw-text-sm">{{
|
||||
(drawerDetails.atRiskMemberDetails.length > 0
|
||||
? "atRiskMembersDescription"
|
||||
: "atRiskMembersDescriptionNone"
|
||||
) | i18n
|
||||
}}</span>
|
||||
<ng-container *ngIf="drawerDetails.atRiskMemberDetails.length > 0">
|
||||
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">{{ "email" | i18n }}</div>
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">
|
||||
{{ "atRiskPasswords" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngFor="let member of drawerDetails.atRiskMemberDetails">
|
||||
<div class="tw-flex tw-justify-between tw-mt-2">
|
||||
<div>{{ member.email }}</div>
|
||||
<div>{{ member.atRiskPasswordCount }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</bit-drawer-body>
|
||||
</ng-container>
|
||||
|
||||
@if (dataService.isActiveDrawerType(drawerTypes.AppAtRiskMembers)) {
|
||||
<bit-drawer-header title="{{ drawerDetails.appAtRiskMembers.applicationName }}">
|
||||
</bit-drawer-header>
|
||||
<bit-drawer-body>
|
||||
<div bitTypography="body1" class="tw-mb-2">
|
||||
{{ "atRiskMembersWithCount" | i18n: drawerDetails.appAtRiskMembers.members.length }}
|
||||
</div>
|
||||
<ng-container *ngFor="let member of dataService.atRiskMemberDetails">
|
||||
<div class="tw-flex tw-justify-between tw-mt-2">
|
||||
<div bitTypography="body1" class="tw-text-muted tw-text-sm tw-mb-2">
|
||||
{{
|
||||
(drawerDetails.appAtRiskMembers.members.length > 0
|
||||
? "atRiskMembersDescriptionWithApp"
|
||||
: "atRiskMembersDescriptionWithAppNone"
|
||||
) | i18n: drawerDetails.appAtRiskMembers.applicationName
|
||||
}}
|
||||
</div>
|
||||
<div class="tw-mt-1">
|
||||
<ng-container *ngFor="let member of drawerDetails.appAtRiskMembers.members">
|
||||
<div>{{ member.email }}</div>
|
||||
<div>{{ member.atRiskPasswordCount }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</bit-drawer-body>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.AppAtRiskMembers)">
|
||||
<bit-drawer-header title="{{ dataService.appAtRiskMembers.applicationName }}">
|
||||
</bit-drawer-header>
|
||||
<bit-drawer-body>
|
||||
<div bitTypography="body1" class="tw-mb-2">
|
||||
{{ "atRiskMembersWithCount" | i18n: dataService.appAtRiskMembers.members.length }}
|
||||
</div>
|
||||
<div bitTypography="body1" class="tw-text-muted tw-text-sm tw-mb-2">
|
||||
{{
|
||||
(dataService.appAtRiskMembers.members.length > 0
|
||||
? "atRiskMembersDescriptionWithApp"
|
||||
: "atRiskMembersDescriptionWithAppNone"
|
||||
) | i18n: dataService.appAtRiskMembers.applicationName
|
||||
}}
|
||||
</div>
|
||||
<div class="tw-mt-1">
|
||||
<ng-container *ngFor="let member of dataService.appAtRiskMembers.members">
|
||||
<div>{{ member.email }}</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</bit-drawer-body>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.OrgAtRiskApps)">
|
||||
<bit-drawer-header
|
||||
title="{{ 'atRiskApplicationsWithCount' | i18n: dataService.atRiskAppDetails.length }}"
|
||||
>
|
||||
</bit-drawer-header>
|
||||
|
||||
<bit-drawer-body>
|
||||
<span bitTypography="body2" class="tw-text-muted tw-text-sm">{{
|
||||
(dataService.atRiskAppDetails.length > 0
|
||||
? "atRiskApplicationsDescription"
|
||||
: "atRiskApplicationsDescriptionNone"
|
||||
) | i18n
|
||||
}}</span>
|
||||
<ng-container *ngIf="dataService.atRiskAppDetails.length > 0">
|
||||
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">
|
||||
{{ "application" | i18n }}
|
||||
</div>
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">
|
||||
{{ "atRiskPasswords" | i18n }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-container *ngFor="let app of dataService.atRiskAppDetails">
|
||||
<div class="tw-flex tw-justify-between tw-mt-2">
|
||||
<div>{{ app.applicationName }}</div>
|
||||
<div>{{ app.atRiskPasswordCount }}</div>
|
||||
</bit-drawer-body>
|
||||
}
|
||||
|
||||
@if (dataService.isActiveDrawerType(drawerTypes.OrgAtRiskApps)) {
|
||||
<bit-drawer-header
|
||||
title="{{ 'atRiskApplicationsWithCount' | i18n: drawerDetails.atRiskAppDetails.length }}"
|
||||
>
|
||||
</bit-drawer-header>
|
||||
|
||||
<bit-drawer-body>
|
||||
<span bitTypography="body2" class="tw-text-muted tw-text-sm">{{
|
||||
(drawerDetails.atRiskAppDetails.length > 0
|
||||
? "atRiskApplicationsDescription"
|
||||
: "atRiskApplicationsDescriptionNone"
|
||||
) | i18n
|
||||
}}</span>
|
||||
<ng-container *ngIf="drawerDetails.atRiskAppDetails.length > 0">
|
||||
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">
|
||||
{{ "application" | i18n }}
|
||||
</div>
|
||||
<div bitTypography="body2" class="tw-text-sm tw-font-bold">
|
||||
{{ "atRiskPasswords" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngFor="let app of drawerDetails.atRiskAppDetails">
|
||||
<div class="tw-flex tw-justify-between tw-mt-2">
|
||||
<div>{{ app.applicationName }}</div>
|
||||
<div>{{ app.atRiskPasswordCount }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</bit-drawer-body>
|
||||
</ng-container>
|
||||
</bit-drawer>
|
||||
</bit-drawer-body>
|
||||
}
|
||||
</bit-drawer>
|
||||
}
|
||||
</ng-container>
|
||||
|
||||
@@ -61,6 +61,9 @@ export enum RiskInsightsTabType {
|
||||
],
|
||||
})
|
||||
export class RiskInsightsComponent implements OnInit {
|
||||
private destroyRef = inject(DestroyRef);
|
||||
private _isDrawerOpen: boolean = false;
|
||||
|
||||
tabIndex: RiskInsightsTabType = RiskInsightsTabType.AllApps;
|
||||
isRiskInsightsActivityTabFeatureEnabled: boolean = false;
|
||||
|
||||
@@ -73,7 +76,6 @@ export class RiskInsightsComponent implements OnInit {
|
||||
notifiedMembersCount: number = 0;
|
||||
|
||||
private organizationId: OrganizationId = "" as OrganizationId;
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
isLoading$: Observable<boolean> = new Observable<boolean>();
|
||||
isRefreshing$: Observable<boolean> = new Observable<boolean>();
|
||||
@@ -136,6 +138,13 @@ export class RiskInsightsComponent implements OnInit {
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Subscribe to drawer state changes
|
||||
this.dataService.drawerDetails$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((details) => {
|
||||
this._isDrawerOpen = details.open;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,4 +172,27 @@ export class RiskInsightsComponent implements OnInit {
|
||||
get drawerTypes(): typeof DrawerType {
|
||||
return DrawerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case getter for syncing drawer state from service to component.
|
||||
* This allows the template to use two-way binding while staying reactive.
|
||||
*/
|
||||
get isDrawerOpen() {
|
||||
return this._isDrawerOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case setter for syncing drawer state from component to service.
|
||||
* When the drawer component closes the drawer, this syncs the state back to the service.
|
||||
*/
|
||||
set isDrawerOpen(value: boolean) {
|
||||
if (this._isDrawerOpen !== value) {
|
||||
this._isDrawerOpen = value;
|
||||
|
||||
// Close the drawer in the service if the drawer component closed the drawer
|
||||
if (!value) {
|
||||
this.dataService.closeDrawer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user