mirror of
https://github.com/bitwarden/browser
synced 2026-02-09 05:00:10 +00:00
minor refactors
This commit is contained in:
@@ -108,42 +108,7 @@
|
||||
|
||||
<!-- Health Status (combined cell) -->
|
||||
<td bitCell colspan="2" class="tw-text-center">
|
||||
<div class="tw-flex tw-justify-center tw-gap-2">
|
||||
<!-- Weak -->
|
||||
@if (cipher.weakPassword === true) {
|
||||
<span bitBadge variant="warning" title="{{ 'weak' | i18n }}">W</span>
|
||||
}
|
||||
<!-- Reused -->
|
||||
@if (cipher.reusedPassword === true) {
|
||||
<span bitBadge variant="warning" title="{{ 'reused' | i18n }}">R</span>
|
||||
}
|
||||
<!-- Exposed -->
|
||||
@if (cipher.exposedPassword === true) {
|
||||
<span
|
||||
bitBadge
|
||||
variant="danger"
|
||||
[title]="cipher.exposedCount + ' ' + ('timesExposed' | i18n)"
|
||||
>E</span
|
||||
>
|
||||
}
|
||||
<!-- Healthy -->
|
||||
@if (
|
||||
cipher.status === RiskInsightsItemStatus.Healthy &&
|
||||
!cipher.weakPassword &&
|
||||
!cipher.reusedPassword &&
|
||||
!cipher.exposedPassword
|
||||
) {
|
||||
<i class="bwi bwi-check tw-text-success-600" aria-hidden="true"></i>
|
||||
}
|
||||
<!-- Loading -->
|
||||
@if (cipher.status === null) {
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
aria-hidden="true"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
></i>
|
||||
}
|
||||
</div>
|
||||
<app-cipher-health-badges [cipher]="cipher"></app-cipher-health-badges>
|
||||
</td>
|
||||
|
||||
<!-- Member Count -->
|
||||
@@ -179,6 +144,15 @@
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
} @else if (processingPhase() === ProcessingPhase.Error) {
|
||||
<!-- Error State -->
|
||||
<div class="tw-text-center tw-py-8 tw-text-danger-600">
|
||||
<i class="bwi bwi-error tw-text-4xl tw-mb-4" aria-hidden="true"></i>
|
||||
<p>{{ "errorOccurred" | i18n }}</p>
|
||||
@if (error()) {
|
||||
<p class="tw-text-sm tw-text-muted tw-mt-2">{{ error() }}</p>
|
||||
}
|
||||
</div>
|
||||
} @else if (processingPhase() === ProcessingPhase.Idle) {
|
||||
<!-- Initial State -->
|
||||
<div class="tw-text-center tw-py-8 tw-text-muted">
|
||||
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
import { BadgeModule, TableDataSource, TableModule } from "@bitwarden/components";
|
||||
/* eslint-enable no-restricted-imports */
|
||||
|
||||
import { CipherHealthBadgesComponent } from "../shared/cipher-health-badges.component";
|
||||
|
||||
/**
|
||||
* Applications tab component for the Risk Insights Prototype.
|
||||
*
|
||||
@@ -33,7 +35,7 @@ import { BadgeModule, TableDataSource, TableModule } from "@bitwarden/components
|
||||
selector: "app-risk-insights-prototype-applications",
|
||||
templateUrl: "./risk-insights-prototype-applications.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, JslibModule, TableModule, BadgeModule],
|
||||
imports: [CommonModule, JslibModule, TableModule, BadgeModule, CipherHealthBadgesComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class RiskInsightsPrototypeApplicationsComponent {
|
||||
@@ -53,6 +55,7 @@ export class RiskInsightsPrototypeApplicationsComponent {
|
||||
|
||||
// Processing state
|
||||
readonly processingPhase = this.orchestrator.processingPhase;
|
||||
readonly error = this.orchestrator.error;
|
||||
|
||||
// Results
|
||||
readonly applications = this.orchestrator.applications;
|
||||
|
||||
@@ -116,6 +116,15 @@
|
||||
</td>
|
||||
</ng-template>
|
||||
</bit-table-scroll>
|
||||
} @else if (processingPhase() === ProcessingPhase.Error) {
|
||||
<!-- Error State -->
|
||||
<div class="tw-text-center tw-py-8 tw-text-danger-600">
|
||||
<i class="bwi bwi-error tw-text-4xl tw-mb-4" aria-hidden="true"></i>
|
||||
<p>{{ "errorOccurred" | i18n }}</p>
|
||||
@if (error()) {
|
||||
<p class="tw-text-sm tw-text-muted tw-mt-2">{{ error() }}</p>
|
||||
}
|
||||
</div>
|
||||
} @else if (processingPhase() === ProcessingPhase.Idle) {
|
||||
<!-- Initial State -->
|
||||
<div class="tw-text-center tw-py-8 tw-text-muted">
|
||||
|
||||
@@ -42,6 +42,7 @@ export class RiskInsightsPrototypeItemsComponent {
|
||||
|
||||
// Processing state
|
||||
readonly processingPhase = this.orchestrator.processingPhase;
|
||||
readonly error = this.orchestrator.error;
|
||||
|
||||
// Results
|
||||
readonly items = this.orchestrator.items;
|
||||
|
||||
@@ -105,42 +105,7 @@
|
||||
|
||||
<!-- Health Status (combined cell) -->
|
||||
<td bitCell class="tw-text-center">
|
||||
<div class="tw-flex tw-justify-center tw-gap-2">
|
||||
<!-- Weak -->
|
||||
@if (cipher.weakPassword === true) {
|
||||
<span bitBadge variant="warning" title="{{ 'weak' | i18n }}">W</span>
|
||||
}
|
||||
<!-- Reused -->
|
||||
@if (cipher.reusedPassword === true) {
|
||||
<span bitBadge variant="warning" title="{{ 'reused' | i18n }}">R</span>
|
||||
}
|
||||
<!-- Exposed -->
|
||||
@if (cipher.exposedPassword === true) {
|
||||
<span
|
||||
bitBadge
|
||||
variant="danger"
|
||||
[title]="cipher.exposedCount + ' ' + ('timesExposed' | i18n)"
|
||||
>E</span
|
||||
>
|
||||
}
|
||||
<!-- Healthy -->
|
||||
@if (
|
||||
cipher.status === RiskInsightsItemStatus.Healthy &&
|
||||
!cipher.weakPassword &&
|
||||
!cipher.reusedPassword &&
|
||||
!cipher.exposedPassword
|
||||
) {
|
||||
<i class="bwi bwi-check tw-text-success-600" aria-hidden="true"></i>
|
||||
}
|
||||
<!-- Loading -->
|
||||
@if (cipher.status === null) {
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
aria-hidden="true"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
></i>
|
||||
}
|
||||
</div>
|
||||
<app-cipher-health-badges [cipher]="cipher"></app-cipher-health-badges>
|
||||
</td>
|
||||
|
||||
<!-- Status -->
|
||||
@@ -163,6 +128,15 @@
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
} @else if (processingPhase() === ProcessingPhase.Error) {
|
||||
<!-- Error State -->
|
||||
<div class="tw-text-center tw-py-8 tw-text-danger-600">
|
||||
<i class="bwi bwi-error tw-text-4xl tw-mb-4" aria-hidden="true"></i>
|
||||
<p>{{ "errorOccurred" | i18n }}</p>
|
||||
@if (error()) {
|
||||
<p class="tw-text-sm tw-text-muted tw-mt-2">{{ error() }}</p>
|
||||
}
|
||||
</div>
|
||||
} @else if (processingPhase() === ProcessingPhase.Idle) {
|
||||
<!-- Initial State -->
|
||||
<div class="tw-text-center tw-py-8 tw-text-muted">
|
||||
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
import { BadgeModule, TableDataSource, TableModule } from "@bitwarden/components";
|
||||
/* eslint-enable no-restricted-imports */
|
||||
|
||||
import { CipherHealthBadgesComponent } from "../shared/cipher-health-badges.component";
|
||||
|
||||
/**
|
||||
* Members tab component for the Risk Insights Prototype.
|
||||
*
|
||||
@@ -33,7 +35,7 @@ import { BadgeModule, TableDataSource, TableModule } from "@bitwarden/components
|
||||
selector: "app-risk-insights-prototype-members",
|
||||
templateUrl: "./risk-insights-prototype-members.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, JslibModule, TableModule, BadgeModule],
|
||||
imports: [CommonModule, JslibModule, TableModule, BadgeModule, CipherHealthBadgesComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class RiskInsightsPrototypeMembersComponent {
|
||||
@@ -49,6 +51,7 @@ export class RiskInsightsPrototypeMembersComponent {
|
||||
// Processing state
|
||||
readonly processingPhase = this.orchestrator.processingPhase;
|
||||
readonly memberProgress = this.orchestrator.memberProgress;
|
||||
readonly error = this.orchestrator.error;
|
||||
|
||||
// Results
|
||||
readonly members = this.orchestrator.members;
|
||||
@@ -77,9 +80,6 @@ export class RiskInsightsPrototypeMembersComponent {
|
||||
return new Map(items.map((item) => [item.cipherId, item]));
|
||||
});
|
||||
|
||||
/** Whether we've requested member data to be built (lazy loading trigger) */
|
||||
private hasRequestedMemberData = false;
|
||||
|
||||
// ============================================================================
|
||||
// Lifecycle
|
||||
// ============================================================================
|
||||
@@ -92,12 +92,14 @@ export class RiskInsightsPrototypeMembersComponent {
|
||||
});
|
||||
|
||||
// Effect to trigger lazy loading of member aggregations when phase is ready
|
||||
// Check actual state (members array length) instead of a local flag to handle
|
||||
// component re-creation and state resets correctly
|
||||
effect(() => {
|
||||
const phase = this.processingPhase();
|
||||
const isReady = phase === ProcessingPhase.Complete || phase === ProcessingPhase.RunningHibp;
|
||||
const membersEmpty = this.members().length === 0;
|
||||
|
||||
if (isReady && !this.hasRequestedMemberData) {
|
||||
this.hasRequestedMemberData = true;
|
||||
if (isReady && membersEmpty) {
|
||||
this.orchestrator.ensureMemberAggregationsBuilt();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/* eslint-disable no-restricted-imports -- Prototype feature using licensed services */
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
RiskInsightsItem,
|
||||
RiskInsightsItemStatus,
|
||||
} from "@bitwarden/common/dirt/reports/risk-insights";
|
||||
import { BadgeModule } from "@bitwarden/components";
|
||||
/* eslint-enable no-restricted-imports */
|
||||
|
||||
/**
|
||||
* Shared component for displaying cipher health badges (Weak/Reused/Exposed indicators).
|
||||
*
|
||||
* Used in the Applications and Members tabs' expanded row views to display
|
||||
* consistent health status badges for individual ciphers.
|
||||
*
|
||||
* Displays:
|
||||
* - W badge (warning) for weak passwords
|
||||
* - R badge (warning) for reused passwords
|
||||
* - E badge (danger) for exposed passwords
|
||||
* - Check icon for healthy items
|
||||
* - Spinner for items still loading
|
||||
*/
|
||||
@Component({
|
||||
selector: "app-cipher-health-badges",
|
||||
standalone: true,
|
||||
imports: [CommonModule, JslibModule, BadgeModule],
|
||||
template: `
|
||||
<div class="tw-flex tw-justify-center tw-gap-2">
|
||||
@if (cipher().weakPassword === true) {
|
||||
<span bitBadge variant="warning" title="{{ 'weak' | i18n }}">W</span>
|
||||
}
|
||||
@if (cipher().reusedPassword === true) {
|
||||
<span bitBadge variant="warning" title="{{ 'reused' | i18n }}">R</span>
|
||||
}
|
||||
@if (cipher().exposedPassword === true) {
|
||||
<span
|
||||
bitBadge
|
||||
variant="danger"
|
||||
[title]="cipher().exposedCount + ' ' + ('timesExposed' | i18n)"
|
||||
>E</span
|
||||
>
|
||||
}
|
||||
@if (isHealthy()) {
|
||||
<i class="bwi bwi-check tw-text-success-600" aria-hidden="true"></i>
|
||||
}
|
||||
@if (cipher().status === null) {
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
aria-hidden="true"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
></i>
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CipherHealthBadgesComponent {
|
||||
/** The cipher item to display health badges for */
|
||||
readonly cipher = input.required<RiskInsightsItem>();
|
||||
|
||||
/** Computed signal to determine if the cipher is healthy with no issues */
|
||||
protected readonly isHealthy = computed(() => {
|
||||
const c = this.cipher();
|
||||
return (
|
||||
c.status === RiskInsightsItemStatus.Healthy &&
|
||||
!c.weakPassword &&
|
||||
!c.reusedPassword &&
|
||||
!c.exposedPassword
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { from, Observable, of } from "rxjs";
|
||||
import { catchError, switchMap, tap, last, map } from "rxjs/operators";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -48,6 +49,7 @@ export class RiskInsightsPrototypeOrchestrationService {
|
||||
private readonly cipherAccessMappingService = inject(CipherAccessMappingService);
|
||||
private readonly passwordHealthService = inject(PasswordHealthService);
|
||||
private readonly riskInsightsService = inject(RiskInsightsPrototypeService);
|
||||
private readonly logService = inject(LogService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
// ============================================================================
|
||||
@@ -466,7 +468,8 @@ export class RiskInsightsPrototypeOrchestrationService {
|
||||
}),
|
||||
last(),
|
||||
map((): void => undefined),
|
||||
catchError(() => {
|
||||
catchError((err: unknown) => {
|
||||
this.logService.error("[RiskInsightsPrototype] Error loading member counts:", err);
|
||||
return of(undefined);
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user