mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[PM-14266] - access intelligence - application table (#11801)
* add application table * add critical applications tab * add button. update copy
This commit is contained in:
@@ -16,6 +16,16 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<bit-tab-group [(selectedIndex)]="tabIndex">
|
<bit-tab-group [(selectedIndex)]="tabIndex">
|
||||||
|
<bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
|
||||||
|
<tools-all-applications></tools-all-applications>
|
||||||
|
</bit-tab>
|
||||||
|
<bit-tab>
|
||||||
|
<ng-template bitTabLabel>
|
||||||
|
<i class="bwi bwi-star"></i>
|
||||||
|
{{ "criticalApplicationsWithCount" | i18n: criticalApps.length }}
|
||||||
|
</ng-template>
|
||||||
|
<tools-critical-applications></tools-critical-applications>
|
||||||
|
</bit-tab>
|
||||||
<bit-tab label="Raw Data">
|
<bit-tab label="Raw Data">
|
||||||
<tools-password-health></tools-password-health>
|
<tools-password-health></tools-password-health>
|
||||||
</bit-tab>
|
</bit-tab>
|
||||||
@@ -25,19 +35,7 @@
|
|||||||
<bit-tab label="Raw Data + uri">
|
<bit-tab label="Raw Data + uri">
|
||||||
<tools-password-health-members-uri></tools-password-health-members-uri>
|
<tools-password-health-members-uri></tools-password-health-members-uri>
|
||||||
</bit-tab>
|
</bit-tab>
|
||||||
<!-- <bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
|
<!-- <bit-tab>
|
||||||
<h2 bitTypography="h2">{{ "allApplications" | i18n }}</h2>
|
|
||||||
<tools-application-table></tools-application-table>
|
|
||||||
</bit-tab>
|
|
||||||
<bit-tab>
|
|
||||||
<ng-template bitTabLabel>
|
|
||||||
<i class="bwi bwi-star"></i>
|
|
||||||
{{ "priorityApplicationsWithCount" | i18n: priorityApps.length }}
|
|
||||||
</ng-template>
|
|
||||||
<h2 bitTypography>{{ "priorityApplications" | i18n }}</h2>
|
|
||||||
<tools-application-table></tools-application-table>
|
|
||||||
</bit-tab>
|
|
||||||
<bit-tab>
|
|
||||||
<ng-template bitTabLabel>
|
<ng-template bitTabLabel>
|
||||||
<i class="bwi bwi-envelope"></i>
|
<i class="bwi bwi-envelope"></i>
|
||||||
{{ "notifiedMembersWithCount" | i18n: priorityApps.length }}
|
{{ "notifiedMembersWithCount" | i18n: priorityApps.length }}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/compone
|
|||||||
|
|
||||||
import { HeaderModule } from "../../layouts/header/header.module";
|
import { HeaderModule } from "../../layouts/header/header.module";
|
||||||
|
|
||||||
import { ApplicationTableComponent } from "./application-table.component";
|
import { AllApplicationsComponent } from "./all-applications.component";
|
||||||
|
import { CriticalApplicationsComponent } from "./critical-applications.component";
|
||||||
import { NotifiedMembersTableComponent } from "./notified-members-table.component";
|
import { NotifiedMembersTableComponent } from "./notified-members-table.component";
|
||||||
import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component";
|
import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component";
|
||||||
import { PasswordHealthMembersComponent } from "./password-health-members.component";
|
import { PasswordHealthMembersComponent } from "./password-health-members.component";
|
||||||
@@ -25,10 +26,11 @@ export enum AccessIntelligenceTabType {
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
templateUrl: "./access-intelligence.component.html",
|
templateUrl: "./access-intelligence.component.html",
|
||||||
imports: [
|
imports: [
|
||||||
ApplicationTableComponent,
|
AllApplicationsComponent,
|
||||||
AsyncActionsModule,
|
AsyncActionsModule,
|
||||||
ButtonModule,
|
ButtonModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
CriticalApplicationsComponent,
|
||||||
JslibModule,
|
JslibModule,
|
||||||
HeaderModule,
|
HeaderModule,
|
||||||
PasswordHealthComponent,
|
PasswordHealthComponent,
|
||||||
@@ -43,7 +45,7 @@ export class AccessIntelligenceComponent {
|
|||||||
dataLastUpdated = new Date();
|
dataLastUpdated = new Date();
|
||||||
|
|
||||||
apps: any[] = [];
|
apps: any[] = [];
|
||||||
priorityApps: any[] = [];
|
criticalApps: any[] = [];
|
||||||
notifiedMembers: any[] = [];
|
notifiedMembers: any[] = [];
|
||||||
|
|
||||||
async refreshData() {
|
async refreshData() {
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<div *ngIf="loading">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tw-mt-4" *ngIf="!dataSource.data.length">
|
||||||
|
<bit-no-items [icon]="noItemsIcon" class="tw-text-main">
|
||||||
|
<ng-container slot="title">
|
||||||
|
<h2 class="tw-font-semibold mt-4">
|
||||||
|
{{ "noAppsInOrgTitle" | i18n: organization.name }}
|
||||||
|
</h2>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container slot="description">
|
||||||
|
<p class="tw-text-muted">
|
||||||
|
{{ "noAppsInOrgDescription" | i18n }}
|
||||||
|
<a class="text-primary" routerLink="/login">{{ "learnMore" | i18n }}</a>
|
||||||
|
</p>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container slot="button">
|
||||||
|
<button bitButton buttonType="primary" type="button">
|
||||||
|
{{ "createNewLoginItem" | i18n }}
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</bit-no-items>
|
||||||
|
</div>
|
||||||
|
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
|
||||||
|
<h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2>
|
||||||
|
<div class="tw-flex tw-gap-6">
|
||||||
|
<tools-card
|
||||||
|
class="tw-flex-1"
|
||||||
|
[title]="'atRiskMembers' | i18n"
|
||||||
|
[value]="mockAtRiskMembersCount"
|
||||||
|
[maxValue]="mockTotalMembersCount"
|
||||||
|
>
|
||||||
|
</tools-card>
|
||||||
|
<tools-card
|
||||||
|
class="tw-flex-1"
|
||||||
|
[title]="'atRiskApplications' | i18n"
|
||||||
|
[value]="mockAtRiskAppsCount"
|
||||||
|
[maxValue]="mockTotalAppsCount"
|
||||||
|
>
|
||||||
|
</tools-card>
|
||||||
|
</div>
|
||||||
|
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
|
||||||
|
<bit-search
|
||||||
|
[placeholder]="'searchApps' | i18n"
|
||||||
|
class="tw-grow"
|
||||||
|
[formControl]="searchControl"
|
||||||
|
></bit-search>
|
||||||
|
<button class="tw-rounded-lg" type="button" buttonType="secondary" bitButton>
|
||||||
|
<i class="bwi bwi-star-f tw-mr-2"></i>
|
||||||
|
{{ "markAppAsCritical" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<bit-table [dataSource]="dataSource">
|
||||||
|
<ng-container header>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th bitSortable="name" bitCell>{{ "application" | i18n }}</th>
|
||||||
|
<th bitSortable="atRiskPasswords" bitCell>{{ "atRiskPasswords" | i18n }}</th>
|
||||||
|
<th bitSortable="totalPasswords" bitCell>{{ "totalPasswords" | i18n }}</th>
|
||||||
|
<th bitSortable="atRiskMembers" bitCell>{{ "atRiskMembers" | i18n }}</th>
|
||||||
|
<th bitSortable="totalMembers" bitCell>{{ "totalMembers" | i18n }}</th>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template body let-rows$>
|
||||||
|
<tr bitRow *ngFor="let r of rows$ | async; trackBy: trackByFunction">
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
bitCheckbox
|
||||||
|
type="checkbox"
|
||||||
|
[checked]="selectedIds.has(r.id)"
|
||||||
|
(change)="onCheckboxChange(r.id, $event)"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td bitCell>
|
||||||
|
<span>{{ r.name }}</span>
|
||||||
|
</td>
|
||||||
|
<td bitCell>
|
||||||
|
<span>
|
||||||
|
{{ r.atRiskPasswords }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td bitCell>
|
||||||
|
<span>
|
||||||
|
{{ r.totalPasswords }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td bitCell>
|
||||||
|
<span>
|
||||||
|
{{ r.atRiskMembers }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td bitCell data-testid="total-membership">
|
||||||
|
{{ r.totalMembers }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</bit-table>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
|
import { FormControl } from "@angular/forms";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { debounceTime, firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
import {
|
||||||
|
Icons,
|
||||||
|
NoItemsModule,
|
||||||
|
SearchModule,
|
||||||
|
TableDataSource,
|
||||||
|
ToastService,
|
||||||
|
} from "@bitwarden/components";
|
||||||
|
import { CardComponent } from "@bitwarden/tools-card";
|
||||||
|
|
||||||
|
import { HeaderModule } from "../../layouts/header/header.module";
|
||||||
|
import { SharedModule } from "../../shared";
|
||||||
|
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||||
|
|
||||||
|
import { applicationTableMockData } from "./application-table.mock";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: "tools-all-applications",
|
||||||
|
templateUrl: "./all-applications.component.html",
|
||||||
|
imports: [HeaderModule, CardComponent, SearchModule, PipesModule, NoItemsModule, SharedModule],
|
||||||
|
})
|
||||||
|
export class AllApplicationsComponent implements OnInit {
|
||||||
|
protected dataSource = new TableDataSource<any>();
|
||||||
|
protected selectedIds: Set<number> = new Set<number>();
|
||||||
|
protected searchControl = new FormControl("", { nonNullable: true });
|
||||||
|
private destroyRef = inject(DestroyRef);
|
||||||
|
protected loading = false;
|
||||||
|
protected organization: Organization;
|
||||||
|
noItemsIcon = Icons.Security;
|
||||||
|
|
||||||
|
// MOCK DATA
|
||||||
|
protected mockData = applicationTableMockData;
|
||||||
|
protected mockAtRiskMembersCount = 0;
|
||||||
|
protected mockAtRiskAppsCount = 0;
|
||||||
|
protected mockTotalMembersCount = 0;
|
||||||
|
protected mockTotalAppsCount = 0;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.activatedRoute.paramMap
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
map(async (params) => {
|
||||||
|
const organizationId = params.get("organizationId");
|
||||||
|
this.organization = await firstValueFrom(this.organizationService.get$(organizationId));
|
||||||
|
// TODO: use organizationId to fetch data
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected cipherService: CipherService,
|
||||||
|
protected passwordStrengthService: PasswordStrengthServiceAbstraction,
|
||||||
|
protected auditService: AuditService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected activatedRoute: ActivatedRoute,
|
||||||
|
protected toastService: ToastService,
|
||||||
|
protected organizationService: OrganizationService,
|
||||||
|
) {
|
||||||
|
this.dataSource.data = applicationTableMockData;
|
||||||
|
this.searchControl.valueChanges
|
||||||
|
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||||
|
.subscribe((v) => (this.dataSource.filter = v));
|
||||||
|
}
|
||||||
|
|
||||||
|
markAppsAsCritical = async () => {
|
||||||
|
// TODO: Send to API once implemented
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.selectedIds.clear();
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "success",
|
||||||
|
title: null,
|
||||||
|
message: this.i18nService.t("appsMarkedAsCritical"),
|
||||||
|
});
|
||||||
|
resolve(true);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
trackByFunction(_: number, item: CipherView) {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
onCheckboxChange(id: number, event: Event) {
|
||||||
|
const isChecked = (event.target as HTMLInputElement).checked;
|
||||||
|
if (isChecked) {
|
||||||
|
this.selectedIds.add(id);
|
||||||
|
} else {
|
||||||
|
this.selectedIds.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<!-- <bit-table [dataSource]="dataSource"> -->
|
|
||||||
<ng-container header>
|
|
||||||
<tr>
|
|
||||||
<th bitCell>{{ "application" | i18n }}</th>
|
|
||||||
<th bitCell>{{ "atRiskPasswords" | i18n }}</th>
|
|
||||||
<th bitCell>{{ "totalPasswords" | i18n }}</th>
|
|
||||||
<th bitCell>{{ "atRiskMembers" | i18n }}</th>
|
|
||||||
<th bitCell>{{ "totalMembers" | i18n }}</th>
|
|
||||||
</tr>
|
|
||||||
</ng-container>
|
|
||||||
<!-- </bit-table> -->
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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<any>();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.dataSource.data = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
export const applicationTableMockData = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "google.com",
|
||||||
|
atRiskPasswords: 4,
|
||||||
|
totalPasswords: 10,
|
||||||
|
atRiskMembers: 2,
|
||||||
|
totalMembers: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "facebook.com",
|
||||||
|
atRiskPasswords: 3,
|
||||||
|
totalPasswords: 8,
|
||||||
|
atRiskMembers: 1,
|
||||||
|
totalMembers: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "twitter.com",
|
||||||
|
atRiskPasswords: 2,
|
||||||
|
totalPasswords: 6,
|
||||||
|
atRiskMembers: 0,
|
||||||
|
totalMembers: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "linkedin.com",
|
||||||
|
atRiskPasswords: 1,
|
||||||
|
totalPasswords: 4,
|
||||||
|
atRiskMembers: 0,
|
||||||
|
totalMembers: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "instagram.com",
|
||||||
|
atRiskPasswords: 0,
|
||||||
|
totalPasswords: 2,
|
||||||
|
atRiskMembers: 0,
|
||||||
|
totalMembers: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "tiktok.com",
|
||||||
|
atRiskPasswords: 0,
|
||||||
|
totalPasswords: 1,
|
||||||
|
atRiskMembers: 0,
|
||||||
|
totalMembers: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<div *ngIf="loading">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tw-mt-4" *ngIf="!dataSource.data.length">
|
||||||
|
<bit-no-items [icon]="noItemsIcon" class="tw-text-main">
|
||||||
|
<ng-container slot="title">
|
||||||
|
<h2 class="tw-font-semibold mt-4">
|
||||||
|
{{ "noCriticalAppsTitle" | i18n }}
|
||||||
|
</h2>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container slot="description">
|
||||||
|
<p class="tw-text-muted">
|
||||||
|
{{ "noCriticalAppsDescription" | i18n }}
|
||||||
|
</p>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container slot="button">
|
||||||
|
<button bitButton buttonType="primary" type="button">{{ "markCriticalApps" | i18n }}</button>
|
||||||
|
</ng-container>
|
||||||
|
</bit-no-items>
|
||||||
|
</div>
|
||||||
|
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
|
||||||
|
<div class="tw-flex tw-justify-between tw-mb-4">
|
||||||
|
<h2 bitTypography="h2">{{ "criticalApplications" | i18n }}</h2>
|
||||||
|
<button bitButton buttonType="primary" type="button">
|
||||||
|
<i class="bwi bwi-envelope tw-mr-2"></i>
|
||||||
|
{{ "requestPasswordChange" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="tw-flex tw-gap-6">
|
||||||
|
<tools-card
|
||||||
|
class="tw-flex-1"
|
||||||
|
[title]="'atRiskMembers' | i18n"
|
||||||
|
[value]="mockAtRiskMembersCount"
|
||||||
|
[maxValue]="mockTotalMembersCount"
|
||||||
|
>
|
||||||
|
</tools-card>
|
||||||
|
<tools-card
|
||||||
|
class="tw-flex-1"
|
||||||
|
[title]="'atRiskApplications' | i18n"
|
||||||
|
[value]="mockAtRiskAppsCount"
|
||||||
|
[maxValue]="mockTotalAppsCount"
|
||||||
|
>
|
||||||
|
</tools-card>
|
||||||
|
</div>
|
||||||
|
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
|
||||||
|
<bit-search
|
||||||
|
[placeholder]="'searchApps' | i18n"
|
||||||
|
class="tw-grow"
|
||||||
|
[formControl]="searchControl"
|
||||||
|
></bit-search>
|
||||||
|
</div>
|
||||||
|
<bit-table [dataSource]="dataSource">
|
||||||
|
<ng-container header>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th bitSortable="name" bitCell>{{ "application" | i18n }}</th>
|
||||||
|
<th bitSortable="atRiskPasswords" bitCell>{{ "atRiskPasswords" | i18n }}</th>
|
||||||
|
<th bitSortable="totalPasswords" bitCell>{{ "totalPasswords" | i18n }}</th>
|
||||||
|
<th bitSortable="atRiskMembers" bitCell>{{ "atRiskMembers" | i18n }}</th>
|
||||||
|
<th bitSortable="totalMembers" bitCell>{{ "totalMembers" | i18n }}</th>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template body let-rows$>
|
||||||
|
<tr bitRow *ngFor="let r of rows$ | async">
|
||||||
|
<td>
|
||||||
|
<i class="bwi bwi-star-f"></i>
|
||||||
|
</td>
|
||||||
|
<td bitCell>
|
||||||
|
<span>{{ r.name }}</span>
|
||||||
|
</td>
|
||||||
|
<td bitCell>
|
||||||
|
<span>
|
||||||
|
{{ r.atRiskPasswords }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td bitCell>
|
||||||
|
<span>
|
||||||
|
{{ r.totalPasswords }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td bitCell>
|
||||||
|
<span>
|
||||||
|
{{ r.atRiskMembers }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td bitCell data-testid="total-membership">
|
||||||
|
{{ r.totalMembers }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</bit-table>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
|
import { FormControl } from "@angular/forms";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { debounceTime, map } from "rxjs";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { SearchModule, TableDataSource, NoItemsModule, Icons } from "@bitwarden/components";
|
||||||
|
import { CardComponent } from "@bitwarden/tools-card";
|
||||||
|
|
||||||
|
import { HeaderModule } from "../../layouts/header/header.module";
|
||||||
|
import { SharedModule } from "../../shared";
|
||||||
|
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||||
|
|
||||||
|
import { applicationTableMockData } from "./application-table.mock";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: "tools-critical-applications",
|
||||||
|
templateUrl: "./critical-applications.component.html",
|
||||||
|
imports: [CardComponent, HeaderModule, SearchModule, NoItemsModule, PipesModule, SharedModule],
|
||||||
|
})
|
||||||
|
export class CriticalApplicationsComponent implements OnInit {
|
||||||
|
protected dataSource = new TableDataSource<any>();
|
||||||
|
protected selectedIds: Set<number> = new Set<number>();
|
||||||
|
protected searchControl = new FormControl("", { nonNullable: true });
|
||||||
|
private destroyRef = inject(DestroyRef);
|
||||||
|
protected loading = false;
|
||||||
|
noItemsIcon = Icons.Security;
|
||||||
|
// MOCK DATA
|
||||||
|
protected mockAtRiskMembersCount = 0;
|
||||||
|
protected mockAtRiskAppsCount = 0;
|
||||||
|
protected mockTotalMembersCount = 0;
|
||||||
|
protected mockTotalAppsCount = 0;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.activatedRoute.paramMap
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
map(async (params) => {
|
||||||
|
// const organizationId = params.get("organizationId");
|
||||||
|
// TODO: use organizationId to fetch data
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected activatedRoute: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
this.dataSource.data = applicationTableMockData;
|
||||||
|
this.searchControl.valueChanges
|
||||||
|
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||||
|
.subscribe((v) => (this.dataSource.filter = v));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<bit-no-items [icon]="noItemsIcon" class="tw-text-main">
|
|
||||||
<ng-container slot="title">
|
|
||||||
<h2 class="tw-font-semibold mt-4">
|
|
||||||
{{ "noPriorityApplicationsTitle" | i18n }}
|
|
||||||
</h2>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container slot="description">
|
|
||||||
<p class="tw-text-muted">
|
|
||||||
{{ "noPriorityApplicationsDescription" | i18n }}
|
|
||||||
</p>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container slot="button">
|
|
||||||
<button bitButton buttonType="primary" type="button">{{ "markPriorityApps" | i18n }}</button>
|
|
||||||
</ng-container>
|
|
||||||
</bit-no-items>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
|
||||||
import { Component } from "@angular/core";
|
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
|
||||||
import { ButtonModule, NoItemsModule, Icons } from "@bitwarden/components";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
standalone: true,
|
|
||||||
selector: "tools-no-priority-apps",
|
|
||||||
templateUrl: "no-priority-apps.component.html",
|
|
||||||
imports: [ButtonModule, CommonModule, JslibModule, NoItemsModule],
|
|
||||||
})
|
|
||||||
export class NoPriorityAppsComponent {
|
|
||||||
noItemsIcon = Icons.Security;
|
|
||||||
}
|
|
||||||
@@ -7,9 +7,6 @@
|
|||||||
></i>
|
></i>
|
||||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-mt-4" *ngIf="!dataSource.data.length">
|
|
||||||
<tools-no-priority-apps></tools-no-priority-apps>
|
|
||||||
</div>
|
|
||||||
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
|
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
|
||||||
<div class="tw-flex tw-gap-6">
|
<div class="tw-flex tw-gap-6">
|
||||||
<tools-card
|
<tools-card
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import { OrganizationBadgeModule } from "../../vault/individual-vault/organizati
|
|||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||||
|
|
||||||
import { NoPriorityAppsComponent } from "./no-priority-apps.component";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
selector: "tools-password-health-members",
|
selector: "tools-password-health-members",
|
||||||
@@ -40,7 +38,6 @@ import { NoPriorityAppsComponent } from "./no-priority-apps.component";
|
|||||||
HeaderModule,
|
HeaderModule,
|
||||||
SearchModule,
|
SearchModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NoPriorityAppsComponent,
|
|
||||||
SharedModule,
|
SharedModule,
|
||||||
TableModule,
|
TableModule,
|
||||||
],
|
],
|
||||||
@@ -100,7 +97,7 @@ export class PasswordHealthMembersComponent implements OnInit {
|
|||||||
|
|
||||||
await passwordHealthService.generateReport();
|
await passwordHealthService.generateReport();
|
||||||
|
|
||||||
this.dataSource.data = []; //passwordHealthService.reportCiphers;
|
this.dataSource.data = passwordHealthService.reportCiphers;
|
||||||
|
|
||||||
this.exposedPasswordMap = passwordHealthService.exposedPasswordMap;
|
this.exposedPasswordMap = passwordHealthService.exposedPasswordMap;
|
||||||
this.passwordStrengthMap = passwordHealthService.passwordStrengthMap;
|
this.passwordStrengthMap = passwordHealthService.passwordStrengthMap;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"allApplications": {
|
"allApplications": {
|
||||||
"message": "All applications"
|
"message": "All applications"
|
||||||
},
|
},
|
||||||
"priorityApplications": {
|
"criticalApplications": {
|
||||||
"message": "Priority applications"
|
"message": "Critical applications"
|
||||||
},
|
},
|
||||||
"accessIntelligence": {
|
"accessIntelligence": {
|
||||||
"message": "Access Intelligence"
|
"message": "Access Intelligence"
|
||||||
@@ -35,8 +35,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"priorityApplicationsWithCount": {
|
"createNewLoginItem": {
|
||||||
"message": "Priority applications ($COUNT$)",
|
"message": "Create new login item"
|
||||||
|
},
|
||||||
|
"criticalApplicationsWithCount": {
|
||||||
|
"message": "Critical applications ($COUNT$)",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {
|
"count": {
|
||||||
"content": "$1",
|
"content": "$1",
|
||||||
@@ -53,14 +56,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noPriorityApplicationsTitle": {
|
"noAppsInOrgTitle": {
|
||||||
"message": "You haven’t marked any applications as a priority"
|
"message": "No applications found in $ORG NAME$",
|
||||||
|
"placeholders": {
|
||||||
|
"org name": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"noPriorityApplicationsDescription": {
|
"noAppsInOrgDescription": {
|
||||||
|
"message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords."
|
||||||
|
},
|
||||||
|
"noCriticalAppsTitle": {
|
||||||
|
"message": "You haven't marked any applications as a Critical"
|
||||||
|
},
|
||||||
|
"noCriticalAppsDescription": {
|
||||||
"message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords."
|
"message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords."
|
||||||
},
|
},
|
||||||
"markPriorityApps": {
|
"markCriticalApps": {
|
||||||
"message": "Mark priority apps"
|
"message": "Mark critical apps"
|
||||||
},
|
},
|
||||||
"markAppAsCritical": {
|
"markAppAsCritical": {
|
||||||
"message": "Mark app as critical"
|
"message": "Mark app as critical"
|
||||||
@@ -74,9 +89,15 @@
|
|||||||
"atRiskPasswords": {
|
"atRiskPasswords": {
|
||||||
"message": "At-risk passwords"
|
"message": "At-risk passwords"
|
||||||
},
|
},
|
||||||
|
"requestPasswordChange": {
|
||||||
|
"message": "Request password change"
|
||||||
|
},
|
||||||
"totalPasswords": {
|
"totalPasswords": {
|
||||||
"message": "Total passwords"
|
"message": "Total passwords"
|
||||||
},
|
},
|
||||||
|
"searchApps": {
|
||||||
|
"message": "Search applications"
|
||||||
|
},
|
||||||
"atRiskMembers": {
|
"atRiskMembers": {
|
||||||
"message": "At-risk members"
|
"message": "At-risk members"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user