mirror of
https://github.com/bitwarden/browser.git
synced 2025-12-05 09:14:28 +01:00
Vertical Vault Navigation (#6957)
* WIP admin console layout * Update icons * Migrate more things * Migrate the last pages * Move header to web * Fix story not working * Convert header component to standalone * Migrate org layout to standalone * Enable org switcher * Add AC to product switcher * Migrate provider portal to vertical nav * Migrate PM * Prettier fixes * Change AC and PP to use secondary variant layout & update logos * Remove full width setting * Remove commented code * Add header to report pages * Add provider portal banner * Fix banner for billing pages * Move vault title to header * Prevent scrollbar jumping * Move send button to header * Replace search input with bit-search * Remove unused files and css * Add banner * Tweak storage option * Fix duplicate nav item after merge * Migrate banner state to state provider framework * [AC-2078] Fix device approvals header * [PM-5861] Hide AC from product switcher for users that do not have access * [PM-5860] Fix Vault and Send page headers * [AC-2075] Fix missing link on reporting nav group * [AC-2079] Hide Payment Method and Billing History pages for self-hosted instances * [AC-2090] Hide reports/event log nav items for users that do not have permission * [AC-2092] Fix missing provider portal option in product switcher on page load * Add null check for organization in org layout component * [AC-2094] Fix missing page header for new client orgs page * [AC-2093] Update New client button styling * Fix failing test after merge * [PM-2087] Use disk-local for web layout banner * [PM-6041] Update banner copy to read "web app" * [PM-6094] Update banner link to marketing URL * [PM-6114] add CL container component to VVR pages (#7802) * create bit-container component * add container to all page components * Fix linting errors after merge with main * Fix product switcher stories * Fix web-header stories * mock org state properly in product switcher stories (#7956) * refactor: move web layout migration banner logic into a service (#7958) * make CL codeowner of web header files * move migration banner logic to service; update stories * [PM-5862] Ensure a sync has run before hiding navigation links * Remove leftover banner global state * Re-add dropped selfHosted ngIf * Add rel noreferrer * Remove comment --------- Co-authored-by: Shane Melton <smelton@bitwarden.com> Co-authored-by: Will Martin <contact@willmartian.com>
This commit is contained in:
parent
a31e3bf842
commit
38d8fbdb5a
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -88,6 +88,7 @@ libs/common/src/autofill @bitwarden/team-autofill-dev
|
||||
## Component Library ##
|
||||
.storybook @bitwarden/team-component-library
|
||||
libs/components @bitwarden/team-component-library
|
||||
apps/web/src/app/layouts/header
|
||||
|
||||
## Desktop native module ##
|
||||
apps/desktop/desktop_native @bitwarden/team-platform-dev
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
<div *ngIf="loaded && activeOrganization != null" class="tw-flex">
|
||||
<button
|
||||
class="tw-flex tw-items-center tw-border-none tw-bg-background-alt"
|
||||
type="button"
|
||||
id="pickerButton"
|
||||
[appA11yTitle]="'organizationPicker' | i18n"
|
||||
[bitMenuTriggerFor]="orgPickerMenu"
|
||||
>
|
||||
<bit-avatar [text]="activeOrganization.name"></bit-avatar>
|
||||
<div class="tw-flex">
|
||||
<div class="org-name tw-ml-3">
|
||||
<span>{{ activeOrganization.name }}</span>
|
||||
<small class="tw-text-muted">{{ "organization" | i18n }}</small>
|
||||
</div>
|
||||
<div class="tw-ml-3">
|
||||
<i class="bwi bwi-angle-down tw-text-main" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div>
|
||||
<div
|
||||
class="tw-ml-3 tw-rounded tw-border tw-border-solid tw-border-danger-500 tw-text-danger"
|
||||
*ngIf="!activeOrganization.enabled"
|
||||
>
|
||||
<div class="tw-px-5 tw-py-2">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "organizationIsDisabled" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tw-ml-3 tw-rounded tw-border tw-border-solid tw-border-info-500 tw-text-info"
|
||||
*ngIf="activeOrganization.isProviderUser"
|
||||
>
|
||||
<div class="tw-px-5 tw-py-2">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "accessingUsingProvider" | i18n: activeOrganization.providerName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bit-menu #orgPickerMenu>
|
||||
<ul aria-labelledby="pickerButton" class="tw-m-0 tw-p-0">
|
||||
<li
|
||||
*ngFor="let org of organizations$ | async"
|
||||
class="tw-flex tw-list-none tw-flex-col"
|
||||
role="none"
|
||||
>
|
||||
<a bitMenuItem [routerLink]="['/organizations', org.id]">
|
||||
<i
|
||||
class="bwi bwi-check mr-2"
|
||||
[ngClass]="org.id === activeOrganization.id ? 'visible' : 'invisible'"
|
||||
>
|
||||
<span class="tw-sr-only">{{ "currentOrganization" | i18n }}</span>
|
||||
</i>
|
||||
{{ org.name }}
|
||||
</a>
|
||||
</li>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
<li class="tw-list-none" role="none">
|
||||
<a bitMenuItem routerLink="/create-organization">
|
||||
<i class="bwi bwi-plus mr-2"></i>
|
||||
{{ "newOrganization" | i18n }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</bit-menu>
|
||||
</div>
|
||||
@ -1,35 +0,0 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { map, Observable } from "rxjs";
|
||||
|
||||
import {
|
||||
canAccessAdmin,
|
||||
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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
@Component({
|
||||
selector: "app-organization-switcher",
|
||||
templateUrl: "organization-switcher.component.html",
|
||||
})
|
||||
export class OrganizationSwitcherComponent implements OnInit {
|
||||
constructor(
|
||||
private organizationService: OrganizationService,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
@Input() activeOrganization: Organization = null;
|
||||
organizations$: Observable<Organization[]>;
|
||||
|
||||
loaded = false;
|
||||
|
||||
async ngOnInit() {
|
||||
this.organizations$ = this.organizationService.memberOrganizations$.pipe(
|
||||
canAccessAdmin(this.i18nService),
|
||||
map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name"))),
|
||||
);
|
||||
|
||||
this.loaded = true;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,41 +1,125 @@
|
||||
<app-navbar></app-navbar>
|
||||
<app-payment-method-banners *ngIf="false"></app-payment-method-banners>
|
||||
<div class="org-nav !tw-h-32" *ngIf="organization$ | async as organization">
|
||||
<div class="container d-flex">
|
||||
<div class="d-flex flex-column">
|
||||
<app-organization-switcher
|
||||
class="my-auto pl-1"
|
||||
[activeOrganization]="organization"
|
||||
></app-organization-switcher>
|
||||
<bit-tab-nav-bar class="-tw-mb-px">
|
||||
<bit-tab-link
|
||||
*ngIf="canShowVaultTab(organization) && organization.flexibleCollections; else vaultTab"
|
||||
route="vault"
|
||||
>{{ "collections" | i18n }}</bit-tab-link
|
||||
>
|
||||
<ng-template #vaultTab>
|
||||
<bit-tab-link *ngIf="canShowVaultTab(organization)" route="vault">{{
|
||||
"vault" | i18n
|
||||
}}</bit-tab-link>
|
||||
</ng-template>
|
||||
<bit-tab-link *ngIf="canShowMembersTab(organization)" route="members">{{
|
||||
"members" | i18n
|
||||
}}</bit-tab-link>
|
||||
<bit-tab-link *ngIf="canShowGroupsTab(organization)" route="groups">{{
|
||||
"groups" | i18n
|
||||
}}</bit-tab-link>
|
||||
<bit-tab-link *ngIf="canShowReportsTab(organization)" route="reporting">
|
||||
{{ getReportTabLabel(organization) | i18n }}
|
||||
</bit-tab-link>
|
||||
<bit-tab-link *ngIf="canShowBillingTab(organization)" route="billing">{{
|
||||
"billing" | i18n
|
||||
}}</bit-tab-link>
|
||||
<bit-tab-link *ngIf="canShowSettingsTab(organization)" route="settings">{{
|
||||
"settings" | i18n
|
||||
}}</bit-tab-link>
|
||||
</bit-tab-nav-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
<app-footer></app-footer>
|
||||
<bit-layout variant="secondary">
|
||||
<nav slot="sidebar" *ngIf="organization$ | async as organization">
|
||||
<a routerLink="." class="tw-m-5 tw-mt-7 tw-block">
|
||||
<bit-icon [icon]="logo"></bit-icon>
|
||||
</a>
|
||||
<org-switcher [filter]="orgFilter"></org-switcher>
|
||||
|
||||
<bit-nav-item
|
||||
icon="bwi-collection"
|
||||
[text]="organization.flexibleCollections ? 'collections' : ('vault' | i18n)"
|
||||
route="vault"
|
||||
*ngIf="canShowVaultTab(organization)"
|
||||
>
|
||||
</bit-nav-item>
|
||||
<bit-nav-item
|
||||
icon="bwi-user"
|
||||
[text]="'members' | i18n"
|
||||
route="members"
|
||||
*ngIf="canShowMembersTab(organization)"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
icon="bwi-users"
|
||||
[text]="'groups' | i18n"
|
||||
route="groups"
|
||||
*ngIf="canShowGroupsTab(organization)"
|
||||
></bit-nav-item>
|
||||
<bit-nav-group
|
||||
icon="bwi-sliders"
|
||||
[text]="getReportTabLabel(organization) | i18n"
|
||||
route="reporting"
|
||||
*ngIf="canShowReportsTab(organization)"
|
||||
>
|
||||
<bit-nav-item
|
||||
[text]="'eventLogs' | i18n"
|
||||
route="reporting/events"
|
||||
*ngIf="organization.canAccessEventLogs"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'reports' | i18n"
|
||||
route="reporting/reports"
|
||||
*ngIf="organization.canAccessReports"
|
||||
></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
<bit-nav-group
|
||||
icon="bwi-billing"
|
||||
[text]="'billing' | i18n"
|
||||
route="billing"
|
||||
*ngIf="canShowBillingTab(organization)"
|
||||
>
|
||||
<bit-nav-item [text]="'subscription' | i18n" route="billing/subscription"></bit-nav-item>
|
||||
<ng-container *ngIf="showPaymentAndHistory$ | async">
|
||||
<bit-nav-item [text]="'paymentMethod' | i18n" route="billing/payment-method"></bit-nav-item>
|
||||
<bit-nav-item [text]="'billingHistory' | i18n" route="billing/history"></bit-nav-item>
|
||||
</ng-container>
|
||||
</bit-nav-group>
|
||||
<bit-nav-group
|
||||
icon="bwi-cog"
|
||||
[text]="'settings' | i18n"
|
||||
route="settings"
|
||||
*ngIf="canShowSettingsTab(organization)"
|
||||
>
|
||||
<bit-nav-item
|
||||
[text]="'organizationInfo' | i18n"
|
||||
route="settings/account"
|
||||
*ngIf="organization.isOwner"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'policies' | i18n"
|
||||
route="settings/policies"
|
||||
*ngIf="organization.canManagePolicies"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'twoStepLogin' | i18n"
|
||||
route="settings/two-factor"
|
||||
*ngIf="organization.use2fa && organization.isOwner"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'importData' | i18n"
|
||||
route="settings/tools/import"
|
||||
*ngIf="organization.canAccessImportExport"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'exportVault' | i18n"
|
||||
route="settings/tools/export"
|
||||
*ngIf="organization.canAccessImportExport"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'domainVerification' | i18n"
|
||||
route="settings/domain-verification"
|
||||
*ngIf="organization?.canManageDomainVerification"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'singleSignOn' | i18n"
|
||||
route="settings/sso"
|
||||
*ngIf="organization.canManageSso"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'deviceApprovals' | i18n"
|
||||
route="settings/device-approvals"
|
||||
*ngIf="organization.canManageDeviceApprovals"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'scim' | i18n"
|
||||
route="settings/scim"
|
||||
*ngIf="organization.canManageScim"
|
||||
></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
</nav>
|
||||
|
||||
<ng-container *ngIf="organization$ | async as organization">
|
||||
<bit-banner
|
||||
*ngIf="organization.isProviderUser"
|
||||
[showClose]="false"
|
||||
class="-tw-m-6 tw-flex tw-flex-col tw-pb-6"
|
||||
>
|
||||
{{ "accessingUsingProvider" | i18n: organization.providerName }}
|
||||
</bit-banner>
|
||||
<app-payment-method-banners
|
||||
*ngIf="false"
|
||||
class="-tw-m-6 tw-flex tw-flex-col tw-pb-6"
|
||||
></app-payment-method-banners>
|
||||
</ng-container>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
</bit-layout>
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||
import { map, mergeMap, Observable, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
canAccessBillingTab,
|
||||
canAccessGroupsTab,
|
||||
@ -13,19 +15,43 @@ import {
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { BannerModule, IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components";
|
||||
|
||||
import { PaymentMethodBannersComponent } from "../../../components/payment-method-banners/payment-method-banners.component";
|
||||
import { OrgSwitcherComponent } from "../../../layouts/org-switcher/org-switcher.component";
|
||||
import { AdminConsoleLogo } from "../../icons/admin-console-logo";
|
||||
|
||||
@Component({
|
||||
selector: "app-organization-layout",
|
||||
templateUrl: "organization-layout.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
JslibModule,
|
||||
LayoutComponent,
|
||||
IconModule,
|
||||
NavigationModule,
|
||||
OrgSwitcherComponent,
|
||||
BannerModule,
|
||||
PaymentMethodBannersComponent,
|
||||
],
|
||||
})
|
||||
export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
protected readonly logo = AdminConsoleLogo;
|
||||
|
||||
protected orgFilter = (org: Organization) => org.isAdmin;
|
||||
|
||||
organization$: Observable<Organization>;
|
||||
showPaymentAndHistory$: Observable<boolean>;
|
||||
|
||||
private _destroy = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private organizationService: OrganizationService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -41,6 +67,15 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
.pipe(getOrganizationById(id));
|
||||
}),
|
||||
);
|
||||
|
||||
this.showPaymentAndHistory$ = this.organization$.pipe(
|
||||
map(
|
||||
(org) =>
|
||||
!this.platformUtilsService.isSelfHost() &&
|
||||
org?.canViewBillingHistory &&
|
||||
org?.canEditPaymentMethods,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<app-header></app-header>
|
||||
|
||||
<div class="tw-mb-4">
|
||||
<h1>{{ "eventLogs" | i18n }}</h1>
|
||||
<div class="tw-mt-4 tw-flex tw-items-center">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "from" | i18n }}</bit-label>
|
||||
|
||||
@ -1,126 +1,114 @@
|
||||
<div class="container page-content">
|
||||
<div class="tw-mb-4 tw-flex">
|
||||
<h1>{{ "groups" | i18n }}</h1>
|
||||
<div class="tw-ml-auto tw-flex tw-items-center">
|
||||
<div class="tw-mr-2">
|
||||
<label class="sr-only">{{ "search" | i18n }}</label>
|
||||
<div class="tw-flex tw-items-center">
|
||||
<i class="bwi bwi-search bwi-fw tw-z-20 -tw-mr-7 tw-text-muted" aria-hidden="true"></i>
|
||||
<app-header>
|
||||
<bit-search
|
||||
[placeholder]="'searchGroups' | i18n"
|
||||
[(ngModel)]="searchText"
|
||||
class="tw-w-80"
|
||||
></bit-search>
|
||||
<button bitButton type="button" buttonType="primary" (click)="add()">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newGroup" | i18n }}
|
||||
</button>
|
||||
</app-header>
|
||||
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loading && visibleGroups">
|
||||
<p *ngIf="!visibleGroups.length">{{ "noGroupsInList" | i18n }}</p>
|
||||
<bit-table
|
||||
*ngIf="visibleGroups.length"
|
||||
infinite-scroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollDisabled]="!isPaging()"
|
||||
(scrolled)="loadMore()"
|
||||
>
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell class="tw-w-20">
|
||||
<input
|
||||
bitInput
|
||||
type="search"
|
||||
placeholder="{{ 'searchGroups' | i18n }}"
|
||||
class="tw-rounded-l tw-pl-9"
|
||||
[(ngModel)]="searchText"
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
class="tw-mr-2"
|
||||
(change)="toggleAllVisible($event)"
|
||||
id="selectAll"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button bitButton type="button" buttonType="primary" (click)="add()">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newGroup" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loading && visibleGroups">
|
||||
<p *ngIf="!visibleGroups.length">{{ "noGroupsInList" | i18n }}</p>
|
||||
<bit-table
|
||||
*ngIf="visibleGroups.length"
|
||||
infinite-scroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollDisabled]="!isPaging()"
|
||||
(scrolled)="loadMore()"
|
||||
>
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell class="tw-w-20">
|
||||
<input
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
class="tw-mr-2"
|
||||
(change)="toggleAllVisible($event)"
|
||||
id="selectAll"
|
||||
/>
|
||||
<label class="tw-mb-0 !tw-font-bold !tw-text-muted" for="selectAll">{{
|
||||
"all" | i18n
|
||||
}}</label>
|
||||
</th>
|
||||
<th bitCell>{{ "name" | i18n }}</th>
|
||||
<th bitCell>{{ "collections" | i18n }}</th>
|
||||
<th bitCell class="tw-w-10">
|
||||
<button
|
||||
[bitMenuTriggerFor]="headerMenu"
|
||||
type="button"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
size="small"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
<label class="tw-mb-0 !tw-font-bold !tw-text-muted" for="selectAll">{{
|
||||
"all" | i18n
|
||||
}}</label>
|
||||
</th>
|
||||
<th bitCell>{{ "name" | i18n }}</th>
|
||||
<th bitCell>{{ "collections" | i18n }}</th>
|
||||
<th bitCell class="tw-w-10">
|
||||
<button
|
||||
[bitMenuTriggerFor]="headerMenu"
|
||||
type="button"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
size="small"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
|
||||
<bit-menu #headerMenu>
|
||||
<button type="button" bitMenuItem (click)="deleteAllSelected()">
|
||||
<span class="tw-text-danger"
|
||||
><i aria-hidden="true" class="bwi bwi-trash"></i> {{ "delete" | i18n }}</span
|
||||
>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let g of visibleGroups">
|
||||
<td bitCell (click)="check(g)" class="tw-cursor-pointer">
|
||||
<input type="checkbox" bitCheckbox [(ngModel)]="g.checked" />
|
||||
</td>
|
||||
<td bitCell class="tw-cursor-pointer tw-font-bold" (click)="edit(g)">
|
||||
<button type="button" bitLink>
|
||||
{{ g.details.name }}
|
||||
<bit-menu #headerMenu>
|
||||
<button type="button" bitMenuItem (click)="deleteAllSelected()">
|
||||
<span class="tw-text-danger"
|
||||
><i aria-hidden="true" class="bwi bwi-trash"></i> {{ "delete" | i18n }}</span
|
||||
>
|
||||
</button>
|
||||
</td>
|
||||
<td bitCell (click)="edit(g, ModalTabType.Collections)" class="tw-cursor-pointer">
|
||||
<bit-badge-list
|
||||
*ngIf="!g.details.accessAll"
|
||||
[items]="g.collectionNames"
|
||||
[maxItems]="3"
|
||||
variant="secondary"
|
||||
></bit-badge-list>
|
||||
<span *ngIf="g.details.accessAll">{{ "all" | i18n }}</span>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<button
|
||||
[bitMenuTriggerFor]="rowMenu"
|
||||
type="button"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
size="small"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
</bit-menu>
|
||||
</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let g of visibleGroups">
|
||||
<td bitCell (click)="check(g)" class="tw-cursor-pointer">
|
||||
<input type="checkbox" bitCheckbox [(ngModel)]="g.checked" />
|
||||
</td>
|
||||
<td bitCell class="tw-cursor-pointer tw-font-bold" (click)="edit(g)">
|
||||
<button type="button" bitLink>
|
||||
{{ g.details.name }}
|
||||
</button>
|
||||
</td>
|
||||
<td bitCell (click)="edit(g, ModalTabType.Collections)" class="tw-cursor-pointer">
|
||||
<bit-badge-list
|
||||
*ngIf="!g.details.accessAll"
|
||||
[items]="g.collectionNames"
|
||||
[maxItems]="3"
|
||||
variant="secondary"
|
||||
></bit-badge-list>
|
||||
<span *ngIf="g.details.accessAll">{{ "all" | i18n }}</span>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<button
|
||||
[bitMenuTriggerFor]="rowMenu"
|
||||
type="button"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
size="small"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
|
||||
<bit-menu #rowMenu>
|
||||
<button type="button" bitMenuItem (click)="edit(g)">
|
||||
<i aria-hidden="true" class="bwi bwi-pencil-square"></i> {{ "editInfo" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="edit(g, ModalTabType.Members)">
|
||||
<i aria-hidden="true" class="bwi bwi-user"></i> {{ "members" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="edit(g, ModalTabType.Collections)">
|
||||
<i aria-hidden="true" class="bwi bwi-collection"></i> {{ "collections" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="delete(g)">
|
||||
<span class="tw-text-danger"
|
||||
><i aria-hidden="true" class="bwi bwi-trash"></i> {{ "delete" | i18n }}</span
|
||||
>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</ng-container>
|
||||
<ng-template #addEdit></ng-template>
|
||||
</div>
|
||||
<bit-menu #rowMenu>
|
||||
<button type="button" bitMenuItem (click)="edit(g)">
|
||||
<i aria-hidden="true" class="bwi bwi-pencil-square"></i> {{ "editInfo" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="edit(g, ModalTabType.Members)">
|
||||
<i aria-hidden="true" class="bwi bwi-user"></i> {{ "members" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="edit(g, ModalTabType.Collections)">
|
||||
<i aria-hidden="true" class="bwi bwi-collection"></i> {{ "collections" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="delete(g)">
|
||||
<span class="tw-text-danger"
|
||||
><i aria-hidden="true" class="bwi bwi-trash"></i> {{ "delete" | i18n }}</span
|
||||
>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</ng-container>
|
||||
<ng-template #addEdit></ng-template>
|
||||
|
||||
@ -1,331 +1,326 @@
|
||||
<div class="container page-content">
|
||||
<div class="tw-mb-4 tw-flex tw-flex-col tw-space-y-4">
|
||||
<h1>{{ "members" | i18n }}</h1>
|
||||
<div class="tw-flex tw-items-center tw-justify-end tw-space-x-3">
|
||||
<bit-toggle-group
|
||||
[selected]="status"
|
||||
(selectedChange)="filter($event)"
|
||||
[attr.aria-label]="'memberStatusFilter' | i18n"
|
||||
>
|
||||
<bit-toggle [value]="null">
|
||||
{{ "all" | i18n }} <span bitBadge variant="info" *ngIf="allCount">{{ allCount }}</span>
|
||||
</bit-toggle>
|
||||
<app-header>
|
||||
<bit-search
|
||||
class="tw-grow"
|
||||
[(ngModel)]="searchText"
|
||||
[placeholder]="'searchMembers' | i18n"
|
||||
></bit-search>
|
||||
|
||||
<bit-toggle [value]="userStatusType.Invited">
|
||||
{{ "invited" | i18n }}
|
||||
<span bitBadge variant="info" *ngIf="invitedCount">{{ invitedCount }}</span>
|
||||
</bit-toggle>
|
||||
<button type="button" bitButton buttonType="primary" (click)="invite()">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "inviteMember" | i18n }}
|
||||
</button>
|
||||
</app-header>
|
||||
|
||||
<bit-toggle [value]="userStatusType.Accepted">
|
||||
{{ "needsConfirmation" | i18n }}
|
||||
<span bitBadge variant="info" *ngIf="acceptedCount">{{ acceptedCount }}</span>
|
||||
</bit-toggle>
|
||||
|
||||
<bit-toggle [value]="userStatusType.Revoked">
|
||||
{{ "revoked" | i18n }}
|
||||
<span bitBadge variant="info" *ngIf="revokedCount">{{ revokedCount }}</span>
|
||||
</bit-toggle>
|
||||
</bit-toggle-group>
|
||||
|
||||
<bit-search
|
||||
class="tw-grow"
|
||||
[(ngModel)]="searchText"
|
||||
[placeholder]="'searchMembers' | i18n"
|
||||
></bit-search>
|
||||
|
||||
<button type="button" bitButton buttonType="primary" (click)="invite()">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "inviteMember" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
!loading &&
|
||||
(isPaging()
|
||||
? pagedUsers
|
||||
: (users | search: searchText : 'name' : 'email' : 'id')) as searchedUsers
|
||||
"
|
||||
<div class="tw-mb-4 tw-flex tw-flex-col tw-space-y-4">
|
||||
<bit-toggle-group
|
||||
[selected]="status"
|
||||
(selectedChange)="filter($event)"
|
||||
[attr.aria-label]="'memberStatusFilter' | i18n"
|
||||
>
|
||||
<p *ngIf="!searchedUsers.length">{{ "noMembersInList" | i18n }}</p>
|
||||
<ng-container *ngIf="searchedUsers.length">
|
||||
<app-callout
|
||||
type="info"
|
||||
title="{{ 'confirmUsers' | i18n }}"
|
||||
icon="bwi bwi-check-circle"
|
||||
*ngIf="showConfirmUsers"
|
||||
>
|
||||
{{ "usersNeedConfirmed" | i18n }}
|
||||
</app-callout>
|
||||
<bit-table
|
||||
infinite-scroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollDisabled]="!isPaging()"
|
||||
(scrolled)="loadMore()"
|
||||
>
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell class="tw-w-20">
|
||||
<input
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
class="tw-mr-1"
|
||||
(change)="selectAll($any($event.target).checked)"
|
||||
id="selectAll"
|
||||
/>
|
||||
<label class="tw-mb-0 !tw-font-bold !tw-text-muted" for="selectAll">{{
|
||||
"all" | i18n
|
||||
}}</label>
|
||||
</th>
|
||||
<th bitCell>{{ "name" | i18n }}</th>
|
||||
<th bitCell>{{ (organization.useGroups ? "groups" : "collections") | i18n }}</th>
|
||||
<th bitCell>{{ "role" | i18n }}</th>
|
||||
<th bitCell>{{ "policies" | i18n }}</th>
|
||||
<th bitCell class="tw-w-10">
|
||||
<button
|
||||
[bitMenuTriggerFor]="headerMenu"
|
||||
type="button"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
size="small"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
<bit-toggle [value]="null">
|
||||
{{ "all" | i18n }} <span bitBadge variant="info" *ngIf="allCount">{{ allCount }}</span>
|
||||
</bit-toggle>
|
||||
|
||||
<bit-menu #headerMenu>
|
||||
<ng-container *ngIf="canUseSecretsManager$ | async">
|
||||
<button type="button" bitMenuItem (click)="bulkEnableSM()">
|
||||
{{ "activateSecretsManager" | i18n }}
|
||||
</button>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
</ng-container>
|
||||
<button type="button" bitMenuItem (click)="bulkReinvite()">
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "reinviteSelected" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="bulkConfirm()"
|
||||
*ngIf="showBulkConfirmUsers"
|
||||
>
|
||||
<span class="tw-text-success">
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "confirmSelected" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="bulkRestore()">
|
||||
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
||||
{{ "restoreAccess" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="bulkRevoke()">
|
||||
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
||||
{{ "revokeAccess" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="bulkRemove()">
|
||||
<span class="tw-text-danger">
|
||||
<i aria-hidden="true" class="bwi bwi-close"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let u of searchedUsers" alignContent="middle">
|
||||
<td bitCell (click)="checkUser(u)">
|
||||
<input type="checkbox" bitCheckbox [(ngModel)]="$any(u).checked" />
|
||||
</td>
|
||||
<td bitCell (click)="edit(u)" class="tw-cursor-pointer">
|
||||
<div class="tw-flex tw-items-center">
|
||||
<bit-avatar
|
||||
size="small"
|
||||
[text]="u | userName"
|
||||
[id]="u.userId"
|
||||
[color]="u.avatarColor"
|
||||
class="tw-mr-3"
|
||||
></bit-avatar>
|
||||
<div class="tw-flex tw-flex-col">
|
||||
<div>
|
||||
<button type="button" bitLink>
|
||||
{{ u.name ?? u.email }}
|
||||
</button>
|
||||
<span
|
||||
bitBadge
|
||||
class="tw-text-xs"
|
||||
variant="secondary"
|
||||
*ngIf="u.status === userStatusType.Invited"
|
||||
>{{ "invited" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
class="tw-text-xs"
|
||||
variant="warning"
|
||||
*ngIf="u.status === userStatusType.Accepted"
|
||||
>{{ "needsConfirmation" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
class="tw-text-xs"
|
||||
variant="secondary"
|
||||
*ngIf="u.status === userStatusType.Revoked"
|
||||
>{{ "revoked" | i18n }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="tw-text-sm tw-text-muted" *ngIf="u.name">
|
||||
{{ u.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<bit-toggle [value]="userStatusType.Invited">
|
||||
{{ "invited" | i18n }}
|
||||
<span bitBadge variant="info" *ngIf="invitedCount">{{ invitedCount }}</span>
|
||||
</bit-toggle>
|
||||
|
||||
<td
|
||||
bitCell
|
||||
(click)="edit(u, organization.useGroups ? memberTab.Groups : memberTab.Collections)"
|
||||
class="tw-cursor-pointer"
|
||||
>
|
||||
<bit-badge-list
|
||||
*ngIf="organization.useGroups || !u.accessAll"
|
||||
[items]="organization.useGroups ? u.groupNames : u.collectionNames"
|
||||
[maxItems]="3"
|
||||
variant="secondary"
|
||||
></bit-badge-list>
|
||||
<span *ngIf="!organization.useGroups && u.accessAll">{{ "all" | i18n }}</span>
|
||||
</td>
|
||||
<bit-toggle [value]="userStatusType.Accepted">
|
||||
{{ "needsConfirmation" | i18n }}
|
||||
<span bitBadge variant="info" *ngIf="acceptedCount">{{ acceptedCount }}</span>
|
||||
</bit-toggle>
|
||||
|
||||
<td
|
||||
bitCell
|
||||
(click)="edit(u, memberTab.Role)"
|
||||
class="tw-cursor-pointer tw-text-sm tw-text-muted"
|
||||
>
|
||||
{{ u.type | userType }}
|
||||
</td>
|
||||
<bit-toggle [value]="userStatusType.Revoked">
|
||||
{{ "revoked" | i18n }}
|
||||
<span bitBadge variant="info" *ngIf="revokedCount">{{ revokedCount }}</span>
|
||||
</bit-toggle>
|
||||
</bit-toggle-group>
|
||||
</div>
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
!loading &&
|
||||
(isPaging()
|
||||
? pagedUsers
|
||||
: (users | search: searchText : 'name' : 'email' : 'id')) as searchedUsers
|
||||
"
|
||||
>
|
||||
<p *ngIf="!searchedUsers.length">{{ "noMembersInList" | i18n }}</p>
|
||||
<ng-container *ngIf="searchedUsers.length">
|
||||
<app-callout
|
||||
type="info"
|
||||
title="{{ 'confirmUsers' | i18n }}"
|
||||
icon="bwi bwi-check-circle"
|
||||
*ngIf="showConfirmUsers"
|
||||
>
|
||||
{{ "usersNeedConfirmed" | i18n }}
|
||||
</app-callout>
|
||||
<bit-table
|
||||
infinite-scroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollDisabled]="!isPaging()"
|
||||
(scrolled)="loadMore()"
|
||||
>
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell class="tw-w-20">
|
||||
<input
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
class="tw-mr-1"
|
||||
(change)="selectAll($any($event.target).checked)"
|
||||
id="selectAll"
|
||||
/>
|
||||
<label class="tw-mb-0 !tw-font-bold !tw-text-muted" for="selectAll">{{
|
||||
"all" | i18n
|
||||
}}</label>
|
||||
</th>
|
||||
<th bitCell>{{ "name" | i18n }}</th>
|
||||
<th bitCell>{{ (organization.useGroups ? "groups" : "collections") | i18n }}</th>
|
||||
<th bitCell>{{ "role" | i18n }}</th>
|
||||
<th bitCell>{{ "policies" | i18n }}</th>
|
||||
<th bitCell class="tw-w-10">
|
||||
<button
|
||||
[bitMenuTriggerFor]="headerMenu"
|
||||
type="button"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
size="small"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
|
||||
<td bitCell class="tw-text-muted">
|
||||
<ng-container *ngIf="u.twoFactorEnabled">
|
||||
<i
|
||||
class="bwi bwi-lock"
|
||||
title="{{ 'userUsingTwoStep' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "userUsingTwoStep" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showEnrolledStatus($any(u))">
|
||||
<i
|
||||
class="bwi bwi-key"
|
||||
title="{{ 'enrolledAccountRecovery' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "enrolledAccountRecovery" | i18n }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<button
|
||||
[bitMenuTriggerFor]="rowMenu"
|
||||
type="button"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
size="small"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
|
||||
<bit-menu #rowMenu>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="reinvite(u)"
|
||||
*ngIf="u.status === userStatusType.Invited"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-envelope"></i>
|
||||
{{ "resendInvitation" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="confirm(u)"
|
||||
*ngIf="u.status === userStatusType.Accepted"
|
||||
>
|
||||
<span class="tw-text-success">
|
||||
<i aria-hidden="true" class="bwi bwi-check"></i> {{ "confirm" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
<bit-menu-divider
|
||||
*ngIf="
|
||||
u.status === userStatusType.Accepted || u.status === userStatusType.Invited
|
||||
"
|
||||
></bit-menu-divider>
|
||||
<button type="button" bitMenuItem (click)="edit(u, memberTab.Role)">
|
||||
<i aria-hidden="true" class="bwi bwi-user"></i> {{ "memberRole" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="edit(u, memberTab.Groups)"
|
||||
*ngIf="organization.useGroups"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-users"></i> {{ "groups" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="edit(u, memberTab.Collections)">
|
||||
<i aria-hidden="true" class="bwi bwi-collection"></i> {{ "collections" | i18n }}
|
||||
<bit-menu #headerMenu>
|
||||
<ng-container *ngIf="canUseSecretsManager$ | async">
|
||||
<button type="button" bitMenuItem (click)="bulkEnableSM()">
|
||||
{{ "activateSecretsManager" | i18n }}
|
||||
</button>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="events(u)"
|
||||
*ngIf="organization.useEvents && u.status === userStatusType.Confirmed"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-file-text"></i> {{ "eventLogs" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="resetPassword(u)"
|
||||
*ngIf="allowResetPassword(u)"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-key"></i> {{ "recoverAccount" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="restore(u)"
|
||||
*ngIf="u.status === userStatusType.Revoked"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-plus-circle"></i>
|
||||
{{ "restoreAccess" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="revoke(u)"
|
||||
*ngIf="u.status !== userStatusType.Revoked"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-minus-circle"></i>
|
||||
{{ "revokeAccess" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(u)">
|
||||
<span class="tw-text-danger">
|
||||
<i aria-hidden="true" class="bwi bwi-close"></i> {{ "remove" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<button type="button" bitMenuItem (click)="bulkReinvite()">
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "reinviteSelected" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="bulkConfirm()"
|
||||
*ngIf="showBulkConfirmUsers"
|
||||
>
|
||||
<span class="tw-text-success">
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "confirmSelected" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="bulkRestore()">
|
||||
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
||||
{{ "restoreAccess" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="bulkRevoke()">
|
||||
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
||||
{{ "revokeAccess" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="bulkRemove()">
|
||||
<span class="tw-text-danger">
|
||||
<i aria-hidden="true" class="bwi bwi-close"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let u of searchedUsers" alignContent="middle">
|
||||
<td bitCell (click)="checkUser(u)">
|
||||
<input type="checkbox" bitCheckbox [(ngModel)]="$any(u).checked" />
|
||||
</td>
|
||||
<td bitCell (click)="edit(u)" class="tw-cursor-pointer">
|
||||
<div class="tw-flex tw-items-center">
|
||||
<bit-avatar
|
||||
size="small"
|
||||
[text]="u | userName"
|
||||
[id]="u.userId"
|
||||
[color]="u.avatarColor"
|
||||
class="tw-mr-3"
|
||||
></bit-avatar>
|
||||
<div class="tw-flex tw-flex-col">
|
||||
<div>
|
||||
<button type="button" bitLink>
|
||||
{{ u.name ?? u.email }}
|
||||
</button>
|
||||
<span
|
||||
bitBadge
|
||||
class="tw-text-xs"
|
||||
variant="secondary"
|
||||
*ngIf="u.status === userStatusType.Invited"
|
||||
>{{ "invited" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
class="tw-text-xs"
|
||||
variant="warning"
|
||||
*ngIf="u.status === userStatusType.Accepted"
|
||||
>{{ "needsConfirmation" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
class="tw-text-xs"
|
||||
variant="secondary"
|
||||
*ngIf="u.status === userStatusType.Revoked"
|
||||
>{{ "revoked" | i18n }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="tw-text-sm tw-text-muted" *ngIf="u.name">
|
||||
{{ u.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td
|
||||
bitCell
|
||||
(click)="edit(u, organization.useGroups ? memberTab.Groups : memberTab.Collections)"
|
||||
class="tw-cursor-pointer"
|
||||
>
|
||||
<bit-badge-list
|
||||
*ngIf="organization.useGroups || !u.accessAll"
|
||||
[items]="organization.useGroups ? u.groupNames : u.collectionNames"
|
||||
[maxItems]="3"
|
||||
variant="secondary"
|
||||
></bit-badge-list>
|
||||
<span *ngIf="!organization.useGroups && u.accessAll">{{ "all" | i18n }}</span>
|
||||
</td>
|
||||
|
||||
<td
|
||||
bitCell
|
||||
(click)="edit(u, memberTab.Role)"
|
||||
class="tw-cursor-pointer tw-text-sm tw-text-muted"
|
||||
>
|
||||
{{ u.type | userType }}
|
||||
</td>
|
||||
|
||||
<td bitCell class="tw-text-muted">
|
||||
<ng-container *ngIf="u.twoFactorEnabled">
|
||||
<i
|
||||
class="bwi bwi-lock"
|
||||
title="{{ 'userUsingTwoStep' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "userUsingTwoStep" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showEnrolledStatus($any(u))">
|
||||
<i
|
||||
class="bwi bwi-key"
|
||||
title="{{ 'enrolledAccountRecovery' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "enrolledAccountRecovery" | i18n }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<button
|
||||
[bitMenuTriggerFor]="rowMenu"
|
||||
type="button"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
size="small"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
|
||||
<bit-menu #rowMenu>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="reinvite(u)"
|
||||
*ngIf="u.status === userStatusType.Invited"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-envelope"></i>
|
||||
{{ "resendInvitation" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="confirm(u)"
|
||||
*ngIf="u.status === userStatusType.Accepted"
|
||||
>
|
||||
<span class="tw-text-success">
|
||||
<i aria-hidden="true" class="bwi bwi-check"></i> {{ "confirm" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
<bit-menu-divider
|
||||
*ngIf="u.status === userStatusType.Accepted || u.status === userStatusType.Invited"
|
||||
></bit-menu-divider>
|
||||
<button type="button" bitMenuItem (click)="edit(u, memberTab.Role)">
|
||||
<i aria-hidden="true" class="bwi bwi-user"></i> {{ "memberRole" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="edit(u, memberTab.Groups)"
|
||||
*ngIf="organization.useGroups"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-users"></i> {{ "groups" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="edit(u, memberTab.Collections)">
|
||||
<i aria-hidden="true" class="bwi bwi-collection"></i> {{ "collections" | i18n }}
|
||||
</button>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="events(u)"
|
||||
*ngIf="organization.useEvents && u.status === userStatusType.Confirmed"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-file-text"></i> {{ "eventLogs" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="resetPassword(u)"
|
||||
*ngIf="allowResetPassword(u)"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-key"></i> {{ "recoverAccount" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="restore(u)"
|
||||
*ngIf="u.status === userStatusType.Revoked"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-plus-circle"></i>
|
||||
{{ "restoreAccess" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="revoke(u)"
|
||||
*ngIf="u.status !== userStatusType.Revoked"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-minus-circle"></i>
|
||||
{{ "revokeAccess" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(u)">
|
||||
<span class="tw-text-danger">
|
||||
<i aria-hidden="true" class="bwi bwi-close"></i> {{ "remove" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</ng-container>
|
||||
<ng-template #addEdit></ng-template>
|
||||
<ng-template #groupsTemplate></ng-template>
|
||||
<ng-template #confirmTemplate></ng-template>
|
||||
<ng-template #resetPasswordTemplate></ng-template>
|
||||
<ng-template #bulkStatusTemplate></ng-template>
|
||||
<ng-template #bulkConfirmTemplate></ng-template>
|
||||
<ng-template #bulkRemoveTemplate></ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #addEdit></ng-template>
|
||||
<ng-template #groupsTemplate></ng-template>
|
||||
<ng-template #confirmTemplate></ng-template>
|
||||
<ng-template #resetPasswordTemplate></ng-template>
|
||||
<ng-template #bulkStatusTemplate></ng-template>
|
||||
<ng-template #bulkConfirmTemplate></ng-template>
|
||||
<ng-template #bulkRemoveTemplate></ng-template>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { LooseComponentsModule } from "../../shared";
|
||||
|
||||
import { CoreOrganizationModule } from "./core";
|
||||
import { GroupAddEditComponent } from "./manage/group-add-edit.component";
|
||||
import { GroupsComponent } from "./manage/groups.component";
|
||||
@ -13,6 +15,7 @@ import { AccessSelectorModule } from "./shared/components/access-selector";
|
||||
AccessSelectorModule,
|
||||
CoreOrganizationModule,
|
||||
OrganizationsRoutingModule,
|
||||
LooseComponentsModule,
|
||||
],
|
||||
declarations: [GroupsComponent, GroupAddEditComponent],
|
||||
})
|
||||
|
||||
@ -1,25 +1,26 @@
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "policies" | i18n }}</h1>
|
||||
</div>
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<table class="table table-hover table-list" *ngIf="!loading">
|
||||
<tbody>
|
||||
<tr *ngFor="let p of policies">
|
||||
<td *ngIf="p.display(organization)">
|
||||
<a href="#" appStopClick (click)="edit(p)">{{ p.name | i18n }}</a>
|
||||
<span bitBadge variant="success" *ngIf="policiesEnabledMap.get(p.type)">{{
|
||||
"on" | i18n
|
||||
}}</span>
|
||||
<small class="text-muted d-block">{{ p.description | i18n }}</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-template #editTemplate></ng-template>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<table class="table table-hover table-list" *ngIf="!loading">
|
||||
<tbody>
|
||||
<tr *ngFor="let p of policies">
|
||||
<td *ngIf="p.display(organization)">
|
||||
<a href="#" appStopClick (click)="edit(p)">{{ p.name | i18n }}</a>
|
||||
<span bitBadge variant="success" *ngIf="policiesEnabledMap.get(p.type)">{{
|
||||
"on" | i18n
|
||||
}}</span>
|
||||
<small class="text-muted d-block">{{ p.description | i18n }}</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-template #editTemplate></ng-template>
|
||||
</bit-container>
|
||||
|
||||
@ -14,13 +14,11 @@ import { OrganizationPermissionsGuard } from "../guards/org-permissions.guard";
|
||||
import { OrganizationRedirectGuard } from "../guards/org-redirect.guard";
|
||||
import { EventsComponent } from "../manage/events.component";
|
||||
|
||||
import { ReportingComponent } from "./reporting.component";
|
||||
import { ReportsHomeComponent } from "./reports-home.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: ReportingComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: { organizationPermissions: canAccessReportingTab },
|
||||
children: [
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { LooseComponentsModule } from "../../../shared";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
import { ReportsSharedModule } from "../../../tools/reports";
|
||||
|
||||
import { OrganizationReportingRoutingModule } from "./organization-reporting-routing.module";
|
||||
import { ReportingComponent } from "./reporting.component";
|
||||
import { ReportsHomeComponent } from "./reports-home.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, ReportsSharedModule, OrganizationReportingRoutingModule],
|
||||
declarations: [ReportsHomeComponent, ReportingComponent],
|
||||
imports: [
|
||||
SharedModule,
|
||||
ReportsSharedModule,
|
||||
OrganizationReportingRoutingModule,
|
||||
LooseComponentsModule,
|
||||
],
|
||||
declarations: [ReportsHomeComponent],
|
||||
})
|
||||
export class OrganizationReportingModule {}
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3" *ngIf="showLeftNav$ | async">
|
||||
<div class="card" *ngIf="organization$ | async as org">
|
||||
<div class="card-header">{{ "reporting" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a
|
||||
routerLink="events"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="org.canAccessEventLogs"
|
||||
>
|
||||
{{ "eventLogs" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="reports"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="org.canAccessReports"
|
||||
>
|
||||
{{ "reports" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9" [ngClass]="(showLeftNav$ | async) ? 'col-9' : 'col-12'">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,7 +1,5 @@
|
||||
<ng-container *ngIf="homepage$ | async">
|
||||
<div class="page-header">
|
||||
<h1>{{ "reports" | i18n }}</h1>
|
||||
</div>
|
||||
<app-header></app-header>
|
||||
|
||||
<p>{{ "orgsReportsDesc" | i18n }}</p>
|
||||
|
||||
|
||||
@ -1,116 +1,119 @@
|
||||
<h1 bitTypography="h1" class="tw-pb-2.5">{{ "organizationInfo" | i18n }}</h1>
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<form *ngIf="org && !loading" [bitSubmit]="submit" [formGroup]="formGroup">
|
||||
<div class="tw-grid tw-grid-cols-2 tw-gap-5">
|
||||
<div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "organizationName" | i18n }}</bit-label>
|
||||
<input bitInput id="orgName" type="text" formControlName="orgName" />
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "billingEmail" | i18n }}</bit-label>
|
||||
<input bitInput id="billingEmail" formControlName="billingEmail" type="email" />
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "businessName" | i18n }}</bit-label>
|
||||
<input bitInput id="businessName" formControlName="businessName" type="text" />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<bit-avatar [text]="org.name" [id]="org.id" size="large"></bit-avatar>
|
||||
<app-account-fingerprint
|
||||
[fingerprintMaterial]="organizationId"
|
||||
[publicKeyBuffer]="publicKeyBuffer"
|
||||
fingerprintLabel="{{ 'yourOrganizationsFingerprint' | i18n }}"
|
||||
>
|
||||
</app-account-fingerprint>
|
||||
</div>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<ng-container *ngIf="canUseApi">
|
||||
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5">{{ "apiKey" | i18n }}</h1>
|
||||
<p>
|
||||
{{ "apiKeyDesc" | i18n }}
|
||||
<a href="https://docs.bitwarden.com" target="_blank" rel="noreferrer">
|
||||
{{ "learnMore" | i18n }}
|
||||
</a>
|
||||
</p>
|
||||
<button type="button" bitButton buttonType="secondary" (click)="viewApiKey()">
|
||||
{{ "viewApiKey" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" (click)="rotateApiKey()">
|
||||
{{ "rotateApiKey" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<form
|
||||
*ngIf="
|
||||
org && !loading && !org.flexibleCollections && (flexibleCollectionsMigrationEnabled$ | async)
|
||||
"
|
||||
>
|
||||
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5">
|
||||
{{ "collectionManagement" | i18n }}
|
||||
</h1>
|
||||
<p>
|
||||
{{ "collectionEnhancementsDesc" | i18n }}
|
||||
<a href="https://bitwarden.com/help/collection-management" target="_blank" rel="noreferrer">
|
||||
{{ "collectionEnhancementsLearnMore" | i18n }}
|
||||
</a>
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="showConfirmCollectionEnhancementsDialog()"
|
||||
<form *ngIf="org && !loading" [bitSubmit]="submit" [formGroup]="formGroup">
|
||||
<div class="tw-grid tw-grid-cols-2 tw-gap-5">
|
||||
<div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "organizationName" | i18n }}</bit-label>
|
||||
<input bitInput id="orgName" type="text" formControlName="orgName" />
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "billingEmail" | i18n }}</bit-label>
|
||||
<input bitInput id="billingEmail" formControlName="billingEmail" type="email" />
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "businessName" | i18n }}</bit-label>
|
||||
<input bitInput id="businessName" formControlName="businessName" type="text" />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<bit-avatar [text]="org.name" [id]="org.id" size="large"></bit-avatar>
|
||||
<app-account-fingerprint
|
||||
[fingerprintMaterial]="organizationId"
|
||||
[publicKeyBuffer]="publicKeyBuffer"
|
||||
fingerprintLabel="{{ 'yourOrganizationsFingerprint' | i18n }}"
|
||||
>
|
||||
</app-account-fingerprint>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<ng-container *ngIf="canUseApi">
|
||||
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5">{{ "apiKey" | i18n }}</h1>
|
||||
<p>
|
||||
{{ "apiKeyDesc" | i18n }}
|
||||
<a href="https://docs.bitwarden.com" target="_blank" rel="noreferrer">
|
||||
{{ "learnMore" | i18n }}
|
||||
</a>
|
||||
</p>
|
||||
<button type="button" bitButton buttonType="secondary" (click)="viewApiKey()">
|
||||
{{ "viewApiKey" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" (click)="rotateApiKey()">
|
||||
{{ "rotateApiKey" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<form
|
||||
*ngIf="
|
||||
org && !loading && !org.flexibleCollections && (flexibleCollectionsMigrationEnabled$ | async)
|
||||
"
|
||||
>
|
||||
{{ "enable" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<form
|
||||
*ngIf="org && !loading && org.flexibleCollections"
|
||||
[bitSubmit]="submitCollectionManagement"
|
||||
[formGroup]="collectionManagementFormGroup"
|
||||
>
|
||||
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5">{{ "collectionManagement" | i18n }}</h1>
|
||||
<p>{{ "collectionManagementDesc" | i18n }}</p>
|
||||
<bit-form-control *ngIf="flexibleCollectionsV1Enabled$ | async">
|
||||
<bit-label>{{ "allowAdminAccessToAllCollectionItemsDesc" | i18n }}</bit-label>
|
||||
<input type="checkbox" bitCheckbox formControlName="allowAdminAccessToAllCollectionItems" />
|
||||
</bit-form-control>
|
||||
<bit-form-control>
|
||||
<bit-label>{{ "limitCollectionCreationDeletionDesc" | i18n }}</bit-label>
|
||||
<input type="checkbox" bitCheckbox formControlName="limitCollectionCreationDeletion" />
|
||||
</bit-form-control>
|
||||
<button
|
||||
*ngIf="!selfHosted"
|
||||
type="submit"
|
||||
bitButton
|
||||
bitFormButton
|
||||
buttonType="primary"
|
||||
id="collectionManagementSubmitButton"
|
||||
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5">
|
||||
{{ "collectionManagement" | i18n }}
|
||||
</h1>
|
||||
<p>
|
||||
{{ "collectionEnhancementsDesc" | i18n }}
|
||||
<a href="https://bitwarden.com/help/collection-management" target="_blank" rel="noreferrer">
|
||||
{{ "collectionEnhancementsLearnMore" | i18n }}
|
||||
</a>
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="showConfirmCollectionEnhancementsDialog()"
|
||||
>
|
||||
{{ "enable" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<form
|
||||
*ngIf="org && !loading && org.flexibleCollections"
|
||||
[bitSubmit]="submitCollectionManagement"
|
||||
[formGroup]="collectionManagementFormGroup"
|
||||
>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5">{{ "collectionManagement" | i18n }}</h1>
|
||||
<p>{{ "collectionManagementDesc" | i18n }}</p>
|
||||
<bit-form-control *ngIf="flexibleCollectionsV1Enabled$ | async">
|
||||
<bit-label>{{ "allowAdminAccessToAllCollectionItemsDesc" | i18n }}</bit-label>
|
||||
<input type="checkbox" bitCheckbox formControlName="allowAdminAccessToAllCollectionItems" />
|
||||
</bit-form-control>
|
||||
<bit-form-control>
|
||||
<bit-label>{{ "limitCollectionCreationDeletionDesc" | i18n }}</bit-label>
|
||||
<input type="checkbox" bitCheckbox formControlName="limitCollectionCreationDeletion" />
|
||||
</bit-form-control>
|
||||
<button
|
||||
*ngIf="!selfHosted"
|
||||
type="submit"
|
||||
bitButton
|
||||
bitFormButton
|
||||
buttonType="primary"
|
||||
id="collectionManagementSubmitButton"
|
||||
>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<app-danger-zone>
|
||||
<button type="button" bitButton buttonType="danger" (click)="deleteOrganization()">
|
||||
{{ "deleteOrganization" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="danger" (click)="purgeVault()">
|
||||
{{ "purgeVault" | i18n }}
|
||||
</button>
|
||||
</app-danger-zone>
|
||||
<app-danger-zone>
|
||||
<button type="button" bitButton buttonType="danger" (click)="deleteOrganization()">
|
||||
{{ "deleteOrganization" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="danger" (click)="purgeVault()">
|
||||
{{ "purgeVault" | i18n }}
|
||||
</button>
|
||||
</app-danger-zone>
|
||||
|
||||
<ng-template #purgeOrganizationTemplate></ng-template>
|
||||
<ng-template #apiKeyTemplate></ng-template>
|
||||
<ng-template #rotateApiKeyTemplate></ng-template>
|
||||
<ng-template #purgeOrganizationTemplate></ng-template>
|
||||
<ng-template #apiKeyTemplate></ng-template>
|
||||
<ng-template #rotateApiKeyTemplate></ng-template>
|
||||
</bit-container>
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<tools-import
|
||||
(formDisabled)="this.disabled = $event"
|
||||
(formLoading)="this.loading = $event"
|
||||
(onSuccessfulImport)="this.onSuccessfulImport($event)"
|
||||
organizationId="{{ routeOrgId }}"
|
||||
></tools-import>
|
||||
<button
|
||||
[disabled]="disabled"
|
||||
[loading]="loading"
|
||||
form="import_form_importForm"
|
||||
bitButton
|
||||
type="submit"
|
||||
bitFormButton
|
||||
buttonType="primary"
|
||||
>
|
||||
{{ "importData" | i18n }}
|
||||
</button>
|
||||
</bit-container>
|
||||
@ -0,0 +1,56 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import {
|
||||
canAccessVaultTab,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ImportCollectionServiceAbstraction } from "@bitwarden/importer/core";
|
||||
import { ImportComponent } from "@bitwarden/importer/ui";
|
||||
|
||||
import { LooseComponentsModule, SharedModule } from "../../../shared";
|
||||
import { ImportCollectionAdminService } from "../../../tools/import/import-collection-admin.service";
|
||||
import { CollectionAdminService } from "../../../vault/core/collection-admin.service";
|
||||
|
||||
@Component({
|
||||
templateUrl: "org-import.component.html",
|
||||
standalone: true,
|
||||
imports: [SharedModule, ImportComponent, LooseComponentsModule],
|
||||
providers: [
|
||||
{
|
||||
provide: ImportCollectionServiceAbstraction,
|
||||
useClass: ImportCollectionAdminService,
|
||||
deps: [CollectionAdminService],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class OrgImportComponent implements OnInit {
|
||||
protected routeOrgId: string = null;
|
||||
protected loading = false;
|
||||
protected disabled = false;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private organizationService: OrganizationService,
|
||||
private router: Router,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.routeOrgId = this.route.snapshot.paramMap.get("organizationId");
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that is called after a successful import.
|
||||
*/
|
||||
protected async onSuccessfulImport(organizationId: string): Promise<void> {
|
||||
const organization = await firstValueFrom(this.organizationService.get$(organizationId));
|
||||
if (organization == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (canAccessVaultTab(organization)) {
|
||||
await this.router.navigate(["organizations", organizationId, "vault"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,13 +9,11 @@ import { OrganizationRedirectGuard } from "../../organizations/guards/org-redire
|
||||
import { PoliciesComponent } from "../../organizations/policies";
|
||||
|
||||
import { AccountComponent } from "./account.component";
|
||||
import { SettingsComponent } from "./settings.component";
|
||||
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: SettingsComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: { organizationPermissions: canAccessSettingsTab },
|
||||
children: [
|
||||
@ -49,9 +47,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: "import",
|
||||
loadComponent: () =>
|
||||
import("../../../tools/import/admin-import.component").then(
|
||||
(mod) => mod.AdminImportComponent,
|
||||
),
|
||||
import("./org-import.component").then((mod) => mod.OrgImportComponent),
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "importData",
|
||||
@ -64,6 +60,9 @@ const routes: Routes = [
|
||||
import("../tools/vault-export/org-vault-export.module").then(
|
||||
(m) => m.OrganizationVaultExportModule,
|
||||
),
|
||||
data: {
|
||||
titleId: "exportVault",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -6,7 +6,6 @@ import { PoliciesModule } from "../../organizations/policies";
|
||||
|
||||
import { AccountComponent } from "./account.component";
|
||||
import { OrganizationSettingsRoutingModule } from "./organization-settings-routing.module";
|
||||
import { SettingsComponent } from "./settings.component";
|
||||
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
|
||||
@NgModule({
|
||||
@ -17,6 +16,6 @@ import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
OrganizationSettingsRoutingModule,
|
||||
AccountFingerprintComponent,
|
||||
],
|
||||
declarations: [SettingsComponent, AccountComponent, TwoFactorSetupComponent],
|
||||
declarations: [AccountComponent, TwoFactorSetupComponent],
|
||||
})
|
||||
export class OrganizationSettingsModule {}
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
<div class="container page-content">
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loading">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card mb-4" *ngIf="organization.canAccessImportExport">
|
||||
<div class="card-header">{{ "tools" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="import" class="list-group-item" routerLinkActive="active">
|
||||
{{ "importData" | i18n }}
|
||||
</a>
|
||||
<a routerLink="export" class="list-group-item" routerLinkActive="active">
|
||||
{{ "exportVault" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" *ngIf="organization.canAccessReports">
|
||||
<div class="card-header d-flex">
|
||||
{{ "reports" | i18n }}
|
||||
<div class="ml-auto">
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
bitBadge
|
||||
*ngIf="!accessReports"
|
||||
(click)="upgradeOrganization()"
|
||||
>
|
||||
{{ "upgrade" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a
|
||||
routerLink="exposed-passwords-report"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
{{ "exposedPasswordsReport" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="reused-passwords-report"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
{{ "reusedPasswordsReport" | i18n }}
|
||||
</a>
|
||||
<a routerLink="weak-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{ "weakPasswordsReport" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="unsecured-websites-report"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
{{ "unsecuredWebsitesReport" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="inactive-two-factor-report"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
{{ "inactive2faReport" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
@ -1,8 +1,6 @@
|
||||
<app-navbar></app-navbar>
|
||||
<div class="container page-content">
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "providers" | i18n }}</h1>
|
||||
</div>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p *ngIf="!loaded" class="text-muted">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
@ -29,5 +27,4 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
</bit-container>
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="page-header">
|
||||
<h1>{{ "newOrganization" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{ "newOrganizationDesc" | i18n }}</p>
|
||||
<app-organization-plans></app-organization-plans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p>{{ "newOrganizationDesc" | i18n }}</p>
|
||||
<app-organization-plans></app-organization-plans>
|
||||
</bit-container>
|
||||
|
||||
@ -6,12 +6,13 @@ import { PlanType } from "@bitwarden/common/billing/enums";
|
||||
import { ProductType } from "@bitwarden/common/enums";
|
||||
|
||||
import { OrganizationPlansComponent } from "../../billing";
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
@Component({
|
||||
templateUrl: "create-organization.component.html",
|
||||
standalone: true,
|
||||
imports: [SharedModule, OrganizationPlansComponent],
|
||||
imports: [SharedModule, OrganizationPlansComponent, HeaderModule],
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class CreateOrganizationComponent implements OnInit {
|
||||
|
||||
@ -1,104 +1,105 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "sponsoredFamilies" | i18n }}</h1>
|
||||
</div>
|
||||
<ng-container *ngIf="loading">
|
||||
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loading">
|
||||
<p>
|
||||
{{ "sponsoredFamiliesEligible" | i18n }}
|
||||
</p>
|
||||
<div>
|
||||
{{ "sponsoredFamiliesInclude" | i18n }}:
|
||||
<ul class="inset-list">
|
||||
<li>{{ "sponsoredFamiliesPremiumAccess" | i18n }}</li>
|
||||
<li>{{ "sponsoredFamiliesSharedCollections" | i18n }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
[formGroup]="sponsorshipForm"
|
||||
ngNativeValidate
|
||||
*ngIf="anyOrgsAvailable$ | async"
|
||||
>
|
||||
<div class="form-group col-7">
|
||||
<label for="availableSponsorshipOrg">{{ "familiesSponsoringOrgSelect" | i18n }}</label>
|
||||
<select
|
||||
id="availableSponsorshipOrg"
|
||||
name="Available Sponsorship Organization"
|
||||
formControlName="selectedSponsorshipOrgId"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
<option disabled="true" value="">-- {{ "select" | i18n }} --</option>
|
||||
<option *ngFor="let o of availableSponsorshipOrgs$ | async" [ngValue]="o.id">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-7">
|
||||
<label for="sponsorshipEmail">{{ "sponsoredFamiliesEmail" | i18n }}:</label>
|
||||
<input
|
||||
id="sponsorshipEmail"
|
||||
class="form-control"
|
||||
inputmode="email"
|
||||
formControlName="sponsorshipEmail"
|
||||
name="sponsorshipEmail"
|
||||
required
|
||||
[attr.aria-invalid]="sponsorshipEmailControl.invalid"
|
||||
/>
|
||||
<small
|
||||
aria-errormessage="sponsorshipEmail"
|
||||
*ngIf="sponsorshipEmailControl.errors?.notAllowedValue"
|
||||
class="error-inline"
|
||||
role="alert"
|
||||
>
|
||||
<i class="bwi bwi-error" aria-hidden="true"></i>
|
||||
{{ "cannotSponsorSelf" | i18n }}
|
||||
</small>
|
||||
<small
|
||||
aria-errormessage="sponsorshipEmail"
|
||||
*ngIf="sponsorshipEmailControl.errors?.email"
|
||||
class="error-inline"
|
||||
role="alert"
|
||||
>
|
||||
<i class="bwi bwi-error" aria-hidden="true"></i>
|
||||
{{ "invalidEmail" | i18n }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group col-7">
|
||||
<button class="btn btn-primary btn-submit mt-2" type="submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "redeem" | i18n }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<ng-container *ngIf="anyActiveSponsorships$ | async">
|
||||
<div class="border-bottom">
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ "recipient" | i18n }}</th>
|
||||
<th>{{ "sponsoringOrg" | i18n }}</th>
|
||||
<th>{{ "status" | i18n }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let o of activeSponsorshipOrgs$ | async">
|
||||
<tr
|
||||
sponsoring-org-row
|
||||
[sponsoringOrg]="o"
|
||||
[isSelfHosted]="isSelfHosted"
|
||||
(sponsorshipRemoved)="forceReload()"
|
||||
></tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<small>{{ "sponsoredFamiliesLeaveCopy" | i18n }}</small>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<ng-container *ngIf="loading">
|
||||
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loading">
|
||||
<p>
|
||||
{{ "sponsoredFamiliesEligible" | i18n }}
|
||||
</p>
|
||||
<div>
|
||||
{{ "sponsoredFamiliesInclude" | i18n }}:
|
||||
<ul class="inset-list">
|
||||
<li>{{ "sponsoredFamiliesPremiumAccess" | i18n }}</li>
|
||||
<li>{{ "sponsoredFamiliesSharedCollections" | i18n }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
[formGroup]="sponsorshipForm"
|
||||
ngNativeValidate
|
||||
*ngIf="anyOrgsAvailable$ | async"
|
||||
>
|
||||
<div class="form-group col-7">
|
||||
<label for="availableSponsorshipOrg">{{ "familiesSponsoringOrgSelect" | i18n }}</label>
|
||||
<select
|
||||
id="availableSponsorshipOrg"
|
||||
name="Available Sponsorship Organization"
|
||||
formControlName="selectedSponsorshipOrgId"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
<option disabled="true" value="">-- {{ "select" | i18n }} --</option>
|
||||
<option *ngFor="let o of availableSponsorshipOrgs$ | async" [ngValue]="o.id">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-7">
|
||||
<label for="sponsorshipEmail">{{ "sponsoredFamiliesEmail" | i18n }}:</label>
|
||||
<input
|
||||
id="sponsorshipEmail"
|
||||
class="form-control"
|
||||
inputmode="email"
|
||||
formControlName="sponsorshipEmail"
|
||||
name="sponsorshipEmail"
|
||||
required
|
||||
[attr.aria-invalid]="sponsorshipEmailControl.invalid"
|
||||
/>
|
||||
<small
|
||||
aria-errormessage="sponsorshipEmail"
|
||||
*ngIf="sponsorshipEmailControl.errors?.notAllowedValue"
|
||||
class="error-inline"
|
||||
role="alert"
|
||||
>
|
||||
<i class="bwi bwi-error" aria-hidden="true"></i>
|
||||
{{ "cannotSponsorSelf" | i18n }}
|
||||
</small>
|
||||
<small
|
||||
aria-errormessage="sponsorshipEmail"
|
||||
*ngIf="sponsorshipEmailControl.errors?.email"
|
||||
class="error-inline"
|
||||
role="alert"
|
||||
>
|
||||
<i class="bwi bwi-error" aria-hidden="true"></i>
|
||||
{{ "invalidEmail" | i18n }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group col-7">
|
||||
<button class="btn btn-primary btn-submit mt-2" type="submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "redeem" | i18n }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<ng-container *ngIf="anyActiveSponsorships$ | async">
|
||||
<div class="border-bottom">
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ "recipient" | i18n }}</th>
|
||||
<th>{{ "sponsoringOrg" | i18n }}</th>
|
||||
<th>{{ "status" | i18n }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let o of activeSponsorshipOrgs$ | async">
|
||||
<tr
|
||||
sponsoring-org-row
|
||||
[sponsoringOrg]="o"
|
||||
[isSelfHosted]="isSelfHosted"
|
||||
(sponsorshipRemoved)="forceReload()"
|
||||
></tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<small>{{ "sponsoredFamiliesLeaveCopy" | i18n }}</small>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</bit-container>
|
||||
|
||||
@ -207,11 +207,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
case "showToast":
|
||||
this.showToast(message);
|
||||
break;
|
||||
case "setFullWidth":
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.setFullWidth();
|
||||
break;
|
||||
case "convertAccountToKeyConnector":
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
@ -243,10 +238,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
new DisableSendPolicy(),
|
||||
new SendOptionsPolicy(),
|
||||
]);
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.setFullWidth();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -356,13 +347,4 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.notificationsService.reconnectFromActivity();
|
||||
}
|
||||
}
|
||||
|
||||
private async setFullWidth() {
|
||||
const enableFullWidth = await this.stateService.getEnableFullWidth();
|
||||
if (enableFullWidth) {
|
||||
document.body.classList.add("full-width");
|
||||
} else {
|
||||
document.body.classList.remove("full-width");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,9 +88,6 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
|
||||
ssoLoginService,
|
||||
webAuthnLoginService,
|
||||
);
|
||||
this.onSuccessfulLogin = async () => {
|
||||
this.messagingService.send("setFullWidth");
|
||||
};
|
||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||
this.showPasswordless = flagEnabled("showPasswordless");
|
||||
}
|
||||
|
||||
@ -1,27 +1,28 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "myAccount" | i18n }}</h1>
|
||||
</div>
|
||||
<app-profile></app-profile>
|
||||
<app-header></app-header>
|
||||
|
||||
<div *ngIf="showChangeEmail" class="tw-mt-16">
|
||||
<h1 bitTypography="h1">{{ "changeEmail" | i18n }}</h1>
|
||||
<app-change-email></app-change-email>
|
||||
</div>
|
||||
<bit-container>
|
||||
<app-profile></app-profile>
|
||||
|
||||
<app-danger-zone>
|
||||
<button type="button" bitButton buttonType="danger" (click)="deauthorizeSessions()">
|
||||
{{ "deauthorizeSessions" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="danger" (click)="purgeVault()">
|
||||
{{ "purgeVault" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="danger" (click)="deleteAccount()">
|
||||
{{ "deleteAccount" | i18n }}
|
||||
</button>
|
||||
</app-danger-zone>
|
||||
<div *ngIf="showChangeEmail" class="tw-mt-16">
|
||||
<h1 bitTypography="h1">{{ "changeEmail" | i18n }}</h1>
|
||||
<app-change-email></app-change-email>
|
||||
</div>
|
||||
|
||||
<ng-template #deauthorizeSessionsTemplate></ng-template>
|
||||
<ng-template #purgeVaultTemplate></ng-template>
|
||||
<ng-template #deleteAccountTemplate></ng-template>
|
||||
<ng-template #viewUserApiKeyTemplate></ng-template>
|
||||
<ng-template #rotateUserApiKeyTemplate></ng-template>
|
||||
<app-danger-zone>
|
||||
<button type="button" bitButton buttonType="danger" (click)="deauthorizeSessions()">
|
||||
{{ "deauthorizeSessions" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="danger" (click)="purgeVault()">
|
||||
{{ "purgeVault" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="danger" (click)="deleteAccount()">
|
||||
{{ "deleteAccount" | i18n }}
|
||||
</button>
|
||||
</app-danger-zone>
|
||||
|
||||
<ng-template #deauthorizeSessionsTemplate></ng-template>
|
||||
<ng-template #purgeVaultTemplate></ng-template>
|
||||
<ng-template #deleteAccountTemplate></ng-template>
|
||||
<ng-template #viewUserApiKeyTemplate></ng-template>
|
||||
<ng-template #rotateUserApiKeyTemplate></ng-template>
|
||||
</bit-container>
|
||||
|
||||
@ -1,249 +1,265 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "emergencyAccess" | i18n }}</h1>
|
||||
</div>
|
||||
<p>
|
||||
{{ "emergencyAccessDesc" | i18n }}
|
||||
<a href="https://bitwarden.com/help/emergency-access/" target="_blank" rel="noreferrer">
|
||||
{{ "learnMore" | i18n }}.
|
||||
</a>
|
||||
</p>
|
||||
<app-header></app-header>
|
||||
|
||||
<p *ngIf="isOrganizationOwner">
|
||||
<b>{{ "warning" | i18n }}:</b> {{ "emergencyAccessOwnerWarning" | i18n }}
|
||||
</p>
|
||||
<bit-container>
|
||||
<p>
|
||||
{{ "emergencyAccessDesc" | i18n }}
|
||||
<a href="https://bitwarden.com/help/emergency-access/" target="_blank" rel="noreferrer">
|
||||
{{ "learnMore" | i18n }}.
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div class="page-header d-flex">
|
||||
<h2>
|
||||
{{ "trustedEmergencyContacts" | i18n }}
|
||||
<app-premium-badge></app-premium-badge>
|
||||
</h2>
|
||||
<div class="ml-auto d-flex">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-primary ml-3"
|
||||
type="button"
|
||||
(click)="invite()"
|
||||
[disabled]="!canAccessPremium"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-plus bwi-fw"></i>
|
||||
{{ "addEmergencyContact" | i18n }}
|
||||
</button>
|
||||
<p *ngIf="isOrganizationOwner">
|
||||
<b>{{ "warning" | i18n }}:</b> {{ "emergencyAccessOwnerWarning" | i18n }}
|
||||
</p>
|
||||
|
||||
<div class="page-header d-flex">
|
||||
<h2>
|
||||
{{ "trustedEmergencyContacts" | i18n }}
|
||||
<app-premium-badge></app-premium-badge>
|
||||
</h2>
|
||||
<div class="ml-auto d-flex">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-primary ml-3"
|
||||
type="button"
|
||||
(click)="invite()"
|
||||
[disabled]="!canAccessPremium"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-plus bwi-fw"></i>
|
||||
{{ "addEmergencyContact" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-hover table-list mb-0" *ngIf="trustedContacts && trustedContacts.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of trustedContacts; let i = index">
|
||||
<td width="30">
|
||||
<bit-avatar
|
||||
[text]="c | userName"
|
||||
[id]="c.granteeId"
|
||||
[color]="c.avatarColor"
|
||||
size="small"
|
||||
></bit-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" appStopClick (click)="edit(c)">{{ c.email }}</a>
|
||||
<span bitBadge variant="secondary" *ngIf="c.status === emergencyAccessStatusType.Invited">{{
|
||||
"invited" | i18n
|
||||
}}</span>
|
||||
<span bitBadge variant="warning" *ngIf="c.status === emergencyAccessStatusType.Accepted">{{
|
||||
"accepted" | i18n
|
||||
}}</span>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||
>
|
||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.RecoveryApproved">{{
|
||||
"emergencyAccessRecoveryApproved" | i18n
|
||||
}}</span>
|
||||
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
"takeover" | i18n
|
||||
}}</span>
|
||||
|
||||
<small class="text-muted d-block" *ngIf="c.name">{{ c.name }}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<button
|
||||
[bitMenuTriggerFor]="trustedContactOptions"
|
||||
class="tw-border-none tw-bg-transparent tw-text-main"
|
||||
type="button"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #trustedContactOptions>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
<table
|
||||
class="table table-hover table-list mb-0"
|
||||
*ngIf="trustedContacts && trustedContacts.length"
|
||||
>
|
||||
<tbody>
|
||||
<tr *ngFor="let c of trustedContacts; let i = index">
|
||||
<td width="30">
|
||||
<bit-avatar
|
||||
[text]="c | userName"
|
||||
[id]="c.granteeId"
|
||||
[color]="c.avatarColor"
|
||||
size="small"
|
||||
></bit-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" appStopClick (click)="edit(c)">{{ c.email }}</a>
|
||||
<span
|
||||
bitBadge
|
||||
variant="secondary"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
||||
(click)="reinvite(c)"
|
||||
>{{ "invited" | i18n }}</span
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "resendInvitation" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
(click)="confirm(c)"
|
||||
>{{ "accepted" | i18n }}</span
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "confirm" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
(click)="approve(c)"
|
||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "approve" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryInitiated ||
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved
|
||||
"
|
||||
(click)="reject(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "reject" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(c)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.RecoveryApproved">{{
|
||||
"emergencyAccessRecoveryApproved" | i18n
|
||||
}}</span>
|
||||
|
||||
<ng-container *ngIf="!trustedContacts || !trustedContacts.length">
|
||||
<p *ngIf="loaded">{{ "noTrustedContacts" | i18n }}</p>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
"takeover" | i18n
|
||||
}}</span>
|
||||
|
||||
<small class="text-muted d-block" *ngIf="c.name">{{ c.name }}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<button
|
||||
[bitMenuTriggerFor]="trustedContactOptions"
|
||||
class="tw-border-none tw-bg-transparent tw-text-main"
|
||||
type="button"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #trustedContactOptions>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
||||
(click)="reinvite(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "resendInvitation" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
(click)="confirm(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "confirm" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
(click)="approve(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "approve" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryInitiated ||
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved
|
||||
"
|
||||
(click)="reject(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "reject" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(c)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ng-container *ngIf="!trustedContacts || !trustedContacts.length">
|
||||
<p *ngIf="loaded">{{ "noTrustedContacts" | i18n }}</p>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<div class="page-header spaced-header">
|
||||
<h2>{{ "designatedEmergencyContacts" | i18n }}</h2>
|
||||
</div>
|
||||
<div class="page-header spaced-header">
|
||||
<h2>{{ "designatedEmergencyContacts" | i18n }}</h2>
|
||||
</div>
|
||||
|
||||
<table class="table table-hover table-list mb-0" *ngIf="grantedContacts && grantedContacts.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of grantedContacts; let i = index">
|
||||
<td width="30">
|
||||
<bit-avatar
|
||||
[text]="c | userName"
|
||||
[id]="c.grantorId"
|
||||
[color]="c.avatarColor"
|
||||
size="small"
|
||||
></bit-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ c.email }}</span>
|
||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.Invited">{{
|
||||
"invited" | i18n
|
||||
}}</span>
|
||||
<span bitBadge variant="warning" *ngIf="c.status === emergencyAccessStatusType.Accepted">{{
|
||||
"accepted" | i18n
|
||||
}}</span>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
variant="success"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved"
|
||||
>{{ "emergencyAccessRecoveryApproved" | i18n }}</span
|
||||
>
|
||||
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
"takeover" | i18n
|
||||
}}</span>
|
||||
|
||||
<small class="text-muted d-block" *ngIf="c.name">{{ c.name }}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<button
|
||||
[bitMenuTriggerFor]="grantedContactOptions"
|
||||
class="tw-border-none tw-bg-transparent tw-text-main"
|
||||
type="button"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #grantedContactOptions>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.Confirmed"
|
||||
(click)="requestAccess(c)"
|
||||
<table
|
||||
class="table table-hover table-list mb-0"
|
||||
*ngIf="grantedContacts && grantedContacts.length"
|
||||
>
|
||||
<tbody>
|
||||
<tr *ngFor="let c of grantedContacts; let i = index">
|
||||
<td width="30">
|
||||
<bit-avatar
|
||||
[text]="c | userName"
|
||||
[id]="c.grantorId"
|
||||
[color]="c.avatarColor"
|
||||
size="small"
|
||||
></bit-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ c.email }}</span>
|
||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.Invited">{{
|
||||
"invited" | i18n
|
||||
}}</span>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
>{{ "accepted" | i18n }}</span
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "requestAccess" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
||||
c.type === emergencyAccessType.Takeover
|
||||
"
|
||||
(click)="takeover(c)"
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
||||
{{ "takeover" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
||||
c.type === emergencyAccessType.View
|
||||
"
|
||||
[routerLink]="c.id"
|
||||
<span
|
||||
bitBadge
|
||||
variant="success"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved"
|
||||
>{{ "emergencyAccessRecoveryApproved" | i18n }}</span
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-eye" aria-hidden="true"></i>
|
||||
{{ "view" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(c)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ng-container *ngIf="!grantedContacts || !grantedContacts.length">
|
||||
<p *ngIf="loaded">{{ "noGrantedAccess" | i18n }}</p>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
"takeover" | i18n
|
||||
}}</span>
|
||||
|
||||
<small class="text-muted d-block" *ngIf="c.name">{{ c.name }}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<button
|
||||
[bitMenuTriggerFor]="grantedContactOptions"
|
||||
class="tw-border-none tw-bg-transparent tw-text-main"
|
||||
type="button"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #grantedContactOptions>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.Confirmed"
|
||||
(click)="requestAccess(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "requestAccess" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
||||
c.type === emergencyAccessType.Takeover
|
||||
"
|
||||
(click)="takeover(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
||||
{{ "takeover" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
||||
c.type === emergencyAccessType.View
|
||||
"
|
||||
[routerLink]="c.id"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-eye" aria-hidden="true"></i>
|
||||
{{ "view" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(c)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ng-container *ngIf="!grantedContacts || !grantedContacts.length">
|
||||
<p *ngIf="loaded">{{ "noGrantedAccess" | i18n }}</p>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</bit-container>
|
||||
|
||||
<ng-template #addEdit></ng-template>
|
||||
<ng-template #takeoverTemplate></ng-template>
|
||||
|
||||
@ -1,22 +1,11 @@
|
||||
<div class="tabbed-nav d-flex flex-column">
|
||||
<ul class="nav nav-tabs">
|
||||
<ng-container *ngIf="showChangePassword">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="change-password" routerLinkActive="active">
|
||||
{{ "masterPassword" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ng-container>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="two-factor" routerLinkActive="active">
|
||||
{{ "twoStepLogin" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="security-keys" routerLinkActive="active">
|
||||
{{ "keys" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
<app-header>
|
||||
<bit-tab-nav-bar slot="tabs">
|
||||
<bit-tab-link route="change-password">{{ "masterPassword" | i18n }}</bit-tab-link>
|
||||
<bit-tab-link route="two-factor">{{ "twoStepLogin" | i18n }}</bit-tab-link>
|
||||
<bit-tab-link route="security-keys">{{ "keys" | i18n }}</bit-tab-link>
|
||||
</bit-tab-nav-bar>
|
||||
</app-header>
|
||||
|
||||
<bit-container>
|
||||
<router-outlet></router-outlet>
|
||||
</bit-container>
|
||||
|
||||
@ -1,79 +1,84 @@
|
||||
<div [ngClass]="tabbedHeader ? 'tabbed-header' : 'page-header'">
|
||||
<h1 *ngIf="!organizationId || !isEnterpriseOrg">{{ "twoStepLogin" | i18n }}</h1>
|
||||
<h1 *ngIf="organizationId && isEnterpriseOrg">{{ "twoStepLoginEnforcement" | i18n }}</h1>
|
||||
</div>
|
||||
<p *ngIf="!organizationId">{{ "twoStepLoginDesc" | i18n }}</p>
|
||||
<ng-container *ngIf="organizationId">
|
||||
<p>
|
||||
<ng-container *ngIf="isEnterpriseOrg; else teamsDescription">
|
||||
{{ "twoStepLoginEnterpriseDescStart" | i18n }}
|
||||
<a routerLink="../policies">{{ "twoStepLoginPolicy" | i18n }}.</a>
|
||||
<br />
|
||||
{{ "twoStepLoginOrganizationDuoDesc" | i18n }}
|
||||
<br />
|
||||
<br />
|
||||
<p>{{ "twoStepLoginOrganizationSsoDesc" | i18n }}</p>
|
||||
</ng-container>
|
||||
<ng-template #teamsDescription>
|
||||
{{ "twoStepLoginTeamsDesc" | i18n }}
|
||||
<br />
|
||||
{{ "twoStepLoginOrganizationDuoDesc" | i18n }}
|
||||
</ng-template>
|
||||
</p>
|
||||
</ng-container>
|
||||
<bit-callout type="warning" *ngIf="!organizationId">
|
||||
<p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p>
|
||||
<button type="button" bitButton buttonType="secondary" (click)="recoveryCode()">
|
||||
{{ "viewRecoveryCode" | i18n }}
|
||||
</button>
|
||||
</bit-callout>
|
||||
<h2 [ngClass]="{ 'mt-5': !organizationId }">
|
||||
{{ "providers" | i18n }}
|
||||
<small *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-fw text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h2>
|
||||
<bit-callout type="warning" *ngIf="showPolicyWarning">
|
||||
{{ "twoStepLoginPolicyUserWarning" | i18n }}
|
||||
</bit-callout>
|
||||
<ul class="list-group list-group-2fa">
|
||||
<li *ngFor="let p of providers" class="list-group-item d-flex align-items-center">
|
||||
<div class="logo-2fa d-flex justify-content-center">
|
||||
<img [class]="'mfaType' + p.type" [alt]="p.name + ' logo'" />
|
||||
</div>
|
||||
<div class="mx-4">
|
||||
<h3 class="mb-0">
|
||||
{{ p.name }}
|
||||
<ng-container *ngIf="p.enabled">
|
||||
<i
|
||||
class="bwi bwi-check text-success bwi-fw"
|
||||
title="{{ 'enabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "enabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
<app-premium-badge *ngIf="p.premium"></app-premium-badge>
|
||||
</h3>
|
||||
{{ p.description }}
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
[disabled]="!canAccessPremium && p.premium"
|
||||
(click)="manage(p.type)"
|
||||
>
|
||||
{{ "manage" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<app-header *ngIf="organizationId != null"></app-header>
|
||||
|
||||
<bit-container>
|
||||
<div class="tabbed-header" *ngIf="organizationId == null">
|
||||
<h1 *ngIf="!organizationId || !isEnterpriseOrg">{{ "twoStepLogin" | i18n }}</h1>
|
||||
<h1 *ngIf="organizationId && isEnterpriseOrg">{{ "twoStepLoginEnforcement" | i18n }}</h1>
|
||||
</div>
|
||||
|
||||
<p *ngIf="!organizationId">{{ "twoStepLoginDesc" | i18n }}</p>
|
||||
<ng-container *ngIf="organizationId">
|
||||
<p>
|
||||
<ng-container *ngIf="isEnterpriseOrg; else teamsDescription">
|
||||
{{ "twoStepLoginEnterpriseDescStart" | i18n }}
|
||||
<a routerLink="../policies">{{ "twoStepLoginPolicy" | i18n }}.</a>
|
||||
<br />
|
||||
{{ "twoStepLoginOrganizationDuoDesc" | i18n }}
|
||||
<br />
|
||||
<br />
|
||||
<p>{{ "twoStepLoginOrganizationSsoDesc" | i18n }}</p>
|
||||
</ng-container>
|
||||
<ng-template #teamsDescription>
|
||||
{{ "twoStepLoginTeamsDesc" | i18n }}
|
||||
<br />
|
||||
{{ "twoStepLoginOrganizationDuoDesc" | i18n }}
|
||||
</ng-template>
|
||||
</p>
|
||||
</ng-container>
|
||||
<bit-callout type="warning" *ngIf="!organizationId">
|
||||
<p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p>
|
||||
<button type="button" bitButton buttonType="secondary" (click)="recoveryCode()">
|
||||
{{ "viewRecoveryCode" | i18n }}
|
||||
</button>
|
||||
</bit-callout>
|
||||
<h2 [ngClass]="{ 'mt-5': !organizationId }">
|
||||
{{ "providers" | i18n }}
|
||||
<small *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-fw text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h2>
|
||||
<bit-callout type="warning" *ngIf="showPolicyWarning">
|
||||
{{ "twoStepLoginPolicyUserWarning" | i18n }}
|
||||
</bit-callout>
|
||||
<ul class="list-group list-group-2fa">
|
||||
<li *ngFor="let p of providers" class="list-group-item d-flex align-items-center">
|
||||
<div class="logo-2fa d-flex justify-content-center">
|
||||
<img [class]="'mfaType' + p.type" [alt]="p.name + ' logo'" />
|
||||
</div>
|
||||
<div class="mx-4">
|
||||
<h3 class="mb-0">
|
||||
{{ p.name }}
|
||||
<ng-container *ngIf="p.enabled">
|
||||
<i
|
||||
class="bwi bwi-check text-success bwi-fw"
|
||||
title="{{ 'enabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "enabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
<app-premium-badge *ngIf="p.premium"></app-premium-badge>
|
||||
</h3>
|
||||
{{ p.description }}
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
[disabled]="!canAccessPremium && p.premium"
|
||||
(click)="manage(p.type)"
|
||||
>
|
||||
{{ "manage" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</bit-container>
|
||||
|
||||
<ng-template #authenticatorTemplate></ng-template>
|
||||
<ng-template #recoveryTemplate></ng-template>
|
||||
|
||||
@ -20,6 +20,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
|
||||
import { RouterService } from "../../core";
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
import { TrialInitiationComponent } from "./trial-initiation.component";
|
||||
import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component";
|
||||
@ -46,6 +47,7 @@ describe("TrialInitiationComponent", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
RouterTestingModule.withRoutes([
|
||||
{ path: "trial", component: TrialInitiationComponent },
|
||||
{
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { BillingSharedModule } from "../shared";
|
||||
|
||||
import { BillingHistoryViewComponent } from "./billing-history-view.component";
|
||||
@ -9,7 +10,7 @@ import { SubscriptionComponent } from "./subscription.component";
|
||||
import { UserSubscriptionComponent } from "./user-subscription.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [IndividualBillingRoutingModule, BillingSharedModule],
|
||||
imports: [IndividualBillingRoutingModule, BillingSharedModule, HeaderModule],
|
||||
declarations: [
|
||||
SubscriptionComponent,
|
||||
BillingHistoryViewComponent,
|
||||
|
||||
@ -1,20 +1,11 @@
|
||||
<div class="tabbed-nav d-flex flex-column" *ngIf="!selfHosted">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" [routerLink]="subscriptionRoute" routerLinkActive="active">
|
||||
{{ "subscription" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="payment-method" routerLinkActive="active">
|
||||
{{ "paymentMethod" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="billing-history" routerLinkActive="active">
|
||||
{{ "billingHistory" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
<app-header>
|
||||
<bit-tab-nav-bar slot="tabs" *ngIf="!selfHosted">
|
||||
<bit-tab-link [route]="subscriptionRoute">{{ "subscription" | i18n }}</bit-tab-link>
|
||||
<bit-tab-link route="payment-method">{{ "paymentMethod" | i18n }}</bit-tab-link>
|
||||
<bit-tab-link route="billing-history">{{ "billingHistory" | i18n }}</bit-tab-link>
|
||||
</bit-tab-nav-bar>
|
||||
</app-header>
|
||||
|
||||
<bit-container>
|
||||
<router-outlet></router-outlet>
|
||||
</bit-container>
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
<div class="d-flex page-header">
|
||||
<h1>
|
||||
{{ "billingHistory" | i18n }}
|
||||
</h1>
|
||||
<app-header>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
@ -14,15 +11,18 @@
|
||||
<i class="bwi bwi-refresh bwi-fw" [ngClass]="{ 'bwi-spin': loading }" aria-hidden="true"></i>
|
||||
{{ "refresh" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<ng-container *ngIf="!firstLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="billing">
|
||||
<app-billing-history [billing]="billing"></app-billing-history>
|
||||
</ng-container>
|
||||
</app-header>
|
||||
|
||||
<bit-container>
|
||||
<ng-container *ngIf="!firstLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="billing">
|
||||
<app-billing-history [billing]="billing"></app-billing-history>
|
||||
</ng-container>
|
||||
</bit-container>
|
||||
|
||||
@ -9,14 +9,12 @@ import { WebPlatformUtilsService } from "../../core/web-platform-utils.service";
|
||||
import { PaymentMethodComponent } from "../shared";
|
||||
|
||||
import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component";
|
||||
import { OrganizationBillingTabComponent } from "./organization-billing-tab.component";
|
||||
import { OrganizationSubscriptionCloudComponent } from "./organization-subscription-cloud.component";
|
||||
import { OrganizationSubscriptionSelfhostComponent } from "./organization-subscription-selfhost.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: OrganizationBillingTabComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: { organizationPermissions: canAccessBillingTab },
|
||||
children: [
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ "billing" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="subscription" class="list-group-item" routerLinkActive="active">
|
||||
{{ "subscription" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
*ngIf="showPaymentAndHistory$ | async"
|
||||
routerLink="payment-method"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
{{ "paymentMethod" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
*ngIf="showPaymentAndHistory$ | async"
|
||||
routerLink="history"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
{{ "billingHistory" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,6 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { UserVerificationModule } from "../../auth/shared/components/user-verification";
|
||||
import { LooseComponentsModule } from "../../shared";
|
||||
import { BillingSharedModule } from "../shared";
|
||||
|
||||
import { AdjustSubscription } from "./adjust-subscription.component";
|
||||
@ -10,7 +11,6 @@ import { ChangePlanComponent } from "./change-plan.component";
|
||||
import { DownloadLicenseComponent } from "./download-license.component";
|
||||
import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component";
|
||||
import { OrganizationBillingRoutingModule } from "./organization-billing-routing.module";
|
||||
import { OrganizationBillingTabComponent } from "./organization-billing-tab.component";
|
||||
import { OrganizationPlansComponent } from "./organization-plans.component";
|
||||
import { OrganizationSubscriptionCloudComponent } from "./organization-subscription-cloud.component";
|
||||
import { OrganizationSubscriptionSelfhostComponent } from "./organization-subscription-selfhost.component";
|
||||
@ -24,6 +24,7 @@ import { SubscriptionHiddenComponent } from "./subscription-hidden.component";
|
||||
UserVerificationModule,
|
||||
BillingSharedModule,
|
||||
OrganizationPlansComponent,
|
||||
LooseComponentsModule,
|
||||
],
|
||||
declarations: [
|
||||
AdjustSubscription,
|
||||
@ -31,7 +32,6 @@ import { SubscriptionHiddenComponent } from "./subscription-hidden.component";
|
||||
BillingSyncKeyComponent,
|
||||
ChangePlanComponent,
|
||||
DownloadLicenseComponent,
|
||||
OrganizationBillingTabComponent,
|
||||
OrganizationSubscriptionCloudComponent,
|
||||
OrganizationSubscriptionSelfhostComponent,
|
||||
OrgBillingHistoryViewComponent,
|
||||
|
||||
@ -1,272 +1,263 @@
|
||||
<div class="tw-mb-2">
|
||||
<h1 bitTypography="h1">
|
||||
{{ "subscription" | i18n }}
|
||||
<small *ngIf="firstLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<ng-container *ngIf="!firstLoaded && loading">
|
||||
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<app-header></app-header>
|
||||
|
||||
<app-org-subscription-hidden
|
||||
*ngIf="firstLoaded && !userOrg.canViewSubscription"
|
||||
[providerName]="userOrg.providerName"
|
||||
></app-org-subscription-hidden>
|
||||
|
||||
<ng-container *ngIf="sub && firstLoaded">
|
||||
<bit-callout
|
||||
type="warning"
|
||||
title="{{ 'canceled' | i18n }}"
|
||||
*ngIf="subscription && subscription.cancelled"
|
||||
>
|
||||
{{ "subscriptionCanceled" | i18n }}</bit-callout
|
||||
>
|
||||
<bit-callout
|
||||
type="warning"
|
||||
title="{{ 'pendingCancellation' | i18n }}"
|
||||
*ngIf="subscriptionMarkedForCancel"
|
||||
>
|
||||
<p>{{ "subscriptionPendingCanceled" | i18n }}</p>
|
||||
<button bitButton buttonType="secondary" [bitAction]="reinstate" type="button">
|
||||
{{ "reinstateSubscription" | i18n }}
|
||||
</button>
|
||||
</bit-callout>
|
||||
|
||||
<dl class="tw-grid tw-grid-flow-col tw-grid-rows-2">
|
||||
<dt>{{ "billingPlan" | i18n }}</dt>
|
||||
<dd>{{ sub.plan.name }}</dd>
|
||||
<ng-container *ngIf="subscription">
|
||||
<dt>{{ "status" | i18n }}</dt>
|
||||
<dd>
|
||||
<span class="tw-capitalize">{{
|
||||
isSponsoredSubscription ? "sponsored" : subscription.status || "-"
|
||||
}}</span>
|
||||
<span bitBadge variant="warning" *ngIf="subscriptionMarkedForCancel">{{
|
||||
"pendingCancellation" | i18n
|
||||
}}</span>
|
||||
</dd>
|
||||
<dt [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||
{{ "subscriptionExpiration" | i18n }}
|
||||
</dt>
|
||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||
{{ nextInvoice ? (nextInvoice.date | date: "mediumDate") : "-" }}
|
||||
</dd>
|
||||
</ng-container>
|
||||
</dl>
|
||||
<ng-container *ngIf="userOrg.canEditSubscription">
|
||||
<div class="tw-flex-col">
|
||||
<strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300">{{
|
||||
"details" | i18n
|
||||
}}</strong>
|
||||
<bit-table>
|
||||
<ng-template body>
|
||||
<ng-container *ngIf="subscription">
|
||||
<tr bitRow *ngFor="let i of subscriptionLineItems">
|
||||
<td bitCell [ngClass]="{ 'tw-pl-20': i.addonSubscriptionItem }">
|
||||
<span *ngIf="!i.addonSubscriptionItem">{{ i.productName | i18n }} -</span>
|
||||
{{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @
|
||||
{{ i.amount | currency: "$" }}
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
sub?.customerDiscount?.appliesTo?.includes(i.productId);
|
||||
else calculateElse
|
||||
"
|
||||
>
|
||||
{{ "freeForOneYear" | i18n }}
|
||||
</ng-container>
|
||||
<ng-template #calculateElse>
|
||||
{{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="userOrg.isFreeOrg">
|
||||
<tr bitRow *ngIf="userOrg.usePasswordManager">
|
||||
<td bitCell>{{ "passwordManager" | i18n }} - {{ "freeOrganization" | i18n }}</td>
|
||||
<td bitCell class="tw-text-right">{{ "free" | i18n }}</td>
|
||||
</tr>
|
||||
<tr bitRow *ngIf="userOrg.useSecretsManager">
|
||||
<td bitCell>{{ "secretsManager" | i18n }} - {{ "freeOrganization" | i18n }}</td>
|
||||
<td bitCell class="tw-text-right">{{ "free" | i18n }}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</div>
|
||||
<bit-container>
|
||||
<ng-container *ngIf="!firstLoaded && loading">
|
||||
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="userOrg.canEditSubscription">
|
||||
<div class="tw-mt-7">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
type="button"
|
||||
(click)="changePlan()"
|
||||
*ngIf="showChangePlanButton"
|
||||
>
|
||||
{{ "changeBillingPlan" | i18n }}
|
||||
<app-org-subscription-hidden
|
||||
*ngIf="firstLoaded && !userOrg.canViewSubscription"
|
||||
[providerName]="userOrg.providerName"
|
||||
></app-org-subscription-hidden>
|
||||
|
||||
<ng-container *ngIf="sub && firstLoaded">
|
||||
<bit-callout
|
||||
type="warning"
|
||||
title="{{ 'canceled' | i18n }}"
|
||||
*ngIf="subscription && subscription.cancelled"
|
||||
>
|
||||
{{ "subscriptionCanceled" | i18n }}</bit-callout
|
||||
>
|
||||
<bit-callout
|
||||
type="warning"
|
||||
title="{{ 'pendingCancellation' | i18n }}"
|
||||
*ngIf="subscriptionMarkedForCancel"
|
||||
>
|
||||
<p>{{ "subscriptionPendingCanceled" | i18n }}</p>
|
||||
<button bitButton buttonType="secondary" [bitAction]="reinstate" type="button">
|
||||
{{ "reinstateSubscription" | i18n }}
|
||||
</button>
|
||||
<app-change-plan
|
||||
[organizationId]="organizationId"
|
||||
[currentPlan]="sub.plan"
|
||||
(onChanged)="closeChangePlan()"
|
||||
(onCanceled)="closeChangePlan()"
|
||||
*ngIf="showChangePlan"
|
||||
></app-change-plan>
|
||||
</div>
|
||||
</ng-container>
|
||||
</bit-callout>
|
||||
|
||||
<ng-container *ngIf="showSecretsManagerSubscribe">
|
||||
<div class="tw-mt-7">
|
||||
<sm-subscribe-standalone
|
||||
[plan]="sub.plan"
|
||||
[organization]="userOrg"
|
||||
[customerDiscount]="customerDiscount"
|
||||
(onSubscribe)="subscriptionAdjusted()"
|
||||
></sm-subscribe-standalone>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="userOrg.canEditSubscription">
|
||||
<h2 bitTypography="h2" class="tw-mt-7">{{ "manageSubscription" | i18n }}</h2>
|
||||
<p bitTypography="body1">{{ subscriptionDesc }}</p>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
subscription && canAdjustSeats && !subscription.cancelled && !subscriptionMarkedForCancel
|
||||
"
|
||||
>
|
||||
<h3 bitTypography="h3" class="tw-mt-7">{{ "passwordManager" | i18n }}</h3>
|
||||
<app-adjust-subscription
|
||||
[seatPrice]="seatPrice"
|
||||
[organizationId]="organizationId"
|
||||
[interval]="billingInterval"
|
||||
[currentSeatCount]="seats"
|
||||
[maxAutoscaleSeats]="maxAutoscaleSeats"
|
||||
(onAdjusted)="subscriptionAdjusted()"
|
||||
>
|
||||
</app-adjust-subscription>
|
||||
</ng-container>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
type="button"
|
||||
[bitAction]="removeSponsorship"
|
||||
*ngIf="isSponsoredSubscription"
|
||||
>
|
||||
{{ "removeSponsorship" | i18n }}
|
||||
</button>
|
||||
<h4 bitTypography="h4" class="tw-mt-9">{{ "storage" | i18n }}</h4>
|
||||
<p bitTypography="body1">
|
||||
{{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }}
|
||||
</p>
|
||||
<bit-progress [barWidth]="storagePercentage" bgColor="success"></bit-progress>
|
||||
<ng-container *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
|
||||
<div class="tw-mt-3">
|
||||
<div class="tw-flex tw-space-x-2" *ngIf="!showAdjustStorage">
|
||||
<button bitButton buttonType="secondary" type="button" (click)="adjustStorage(true)">
|
||||
{{ "addStorage" | i18n }}
|
||||
</button>
|
||||
<button bitButton buttonType="secondary" type="button" (click)="adjustStorage(false)">
|
||||
{{ "removeStorage" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<app-adjust-storage
|
||||
[storageGbPrice]="storageGbPrice"
|
||||
[add]="adjustStorageAdd"
|
||||
[organizationId]="organizationId"
|
||||
[interval]="billingInterval"
|
||||
(onAdjusted)="closeStorage(true)"
|
||||
(onCanceled)="closeStorage(false)"
|
||||
*ngIf="showAdjustStorage"
|
||||
></app-adjust-storage>
|
||||
<dl class="tw-grid tw-grid-flow-col tw-grid-rows-2">
|
||||
<dt>{{ "billingPlan" | i18n }}</dt>
|
||||
<dd>{{ sub.plan.name }}</dd>
|
||||
<ng-container *ngIf="subscription">
|
||||
<dt>{{ "status" | i18n }}</dt>
|
||||
<dd>
|
||||
<span class="tw-capitalize">{{
|
||||
isSponsoredSubscription ? "sponsored" : subscription.status || "-"
|
||||
}}</span>
|
||||
<span bitBadge variant="warning" *ngIf="subscriptionMarkedForCancel">{{
|
||||
"pendingCancellation" | i18n
|
||||
}}</span>
|
||||
</dd>
|
||||
<dt [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||
{{ "subscriptionExpiration" | i18n }}
|
||||
</dt>
|
||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||
{{ nextInvoice ? (nextInvoice.date | date: "mediumDate") : "-" }}
|
||||
</dd>
|
||||
</ng-container>
|
||||
</dl>
|
||||
<ng-container *ngIf="userOrg.canEditSubscription">
|
||||
<div class="tw-flex-col">
|
||||
<strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300">{{
|
||||
"details" | i18n
|
||||
}}</strong>
|
||||
<bit-table>
|
||||
<ng-template body>
|
||||
<ng-container *ngIf="subscription">
|
||||
<tr bitRow *ngFor="let i of subscriptionLineItems">
|
||||
<td bitCell [ngClass]="{ 'tw-pl-20': i.addonSubscriptionItem }">
|
||||
<span *ngIf="!i.addonSubscriptionItem">{{ i.productName | i18n }} -</span>
|
||||
{{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @
|
||||
{{ i.amount | currency: "$" }}
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
sub?.customerDiscount?.appliesTo?.includes(i.productId);
|
||||
else calculateElse
|
||||
"
|
||||
>
|
||||
{{ "freeForOneYear" | i18n }}
|
||||
</ng-container>
|
||||
<ng-template #calculateElse>
|
||||
{{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="userOrg.isFreeOrg">
|
||||
<tr bitRow *ngIf="userOrg.usePasswordManager">
|
||||
<td bitCell>{{ "passwordManager" | i18n }} - {{ "freeOrganization" | i18n }}</td>
|
||||
<td bitCell class="tw-text-right">{{ "free" | i18n }}</td>
|
||||
</tr>
|
||||
<tr bitRow *ngIf="userOrg.useSecretsManager">
|
||||
<td bitCell>{{ "secretsManager" | i18n }} - {{ "freeOrganization" | i18n }}</td>
|
||||
<td bitCell class="tw-text-right">{{ "free" | i18n }}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showAdjustSecretsManager">
|
||||
<h3 bitTypography="h3" class="tw-mt-9">{{ "secretsManager" | i18n }}</h3>
|
||||
<app-sm-adjust-subscription
|
||||
[organizationId]="organizationId"
|
||||
[options]="smOptions"
|
||||
(onAdjusted)="subscriptionAdjusted()"
|
||||
></app-sm-adjust-subscription>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<h2 bitTypography="h2" class="tw-mt-7">{{ "selfHostingTitle" | i18n }}</h2>
|
||||
<p bitTypography="body1">
|
||||
{{ "selfHostingEnterpriseOrganizationSectionCopy" | i18n }}
|
||||
</p>
|
||||
<div class="tw-flex tw-space-x-2">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
type="button"
|
||||
(click)="downloadLicense()"
|
||||
*ngIf="canDownloadLicense"
|
||||
[disabled]="showDownloadLicense"
|
||||
>
|
||||
{{ "downloadLicense" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
type="button"
|
||||
(click)="manageBillingSync()"
|
||||
*ngIf="canManageBillingSync"
|
||||
>
|
||||
{{ (hasBillingSyncToken ? "manageBillingSync" : "setUpBillingSync") | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="tw-mt-3" *ngIf="showDownloadLicense">
|
||||
<app-download-license
|
||||
[organizationId]="organizationId"
|
||||
(onDownloaded)="closeDownloadLicense()"
|
||||
(onCanceled)="closeDownloadLicense()"
|
||||
></app-download-license>
|
||||
</div>
|
||||
<ng-container *ngIf="userOrg.canEditSubscription">
|
||||
<h2 bitTypography="h2" class="tw-mt-7">{{ "additionalOptions" | i18n }}</h2>
|
||||
<ng-container *ngIf="userOrg.canEditSubscription">
|
||||
<div class="tw-mt-7">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
type="button"
|
||||
(click)="changePlan()"
|
||||
*ngIf="showChangePlanButton"
|
||||
>
|
||||
{{ "changeBillingPlan" | i18n }}
|
||||
</button>
|
||||
<app-change-plan
|
||||
[organizationId]="organizationId"
|
||||
[currentPlan]="sub.plan"
|
||||
(onChanged)="closeChangePlan()"
|
||||
(onCanceled)="closeChangePlan()"
|
||||
*ngIf="showChangePlan"
|
||||
></app-change-plan>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="showSecretsManagerSubscribe">
|
||||
<div class="tw-mt-7">
|
||||
<sm-subscribe-standalone
|
||||
[plan]="sub.plan"
|
||||
[organization]="userOrg"
|
||||
[customerDiscount]="customerDiscount"
|
||||
(onSubscribe)="subscriptionAdjusted()"
|
||||
></sm-subscribe-standalone>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="userOrg.canEditSubscription">
|
||||
<h2 bitTypography="h2" class="tw-mt-7">{{ "manageSubscription" | i18n }}</h2>
|
||||
<p bitTypography="body1">{{ subscriptionDesc }}</p>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
subscription && canAdjustSeats && !subscription.cancelled && !subscriptionMarkedForCancel
|
||||
"
|
||||
>
|
||||
<h3 bitTypography="h3" class="tw-mt-7">{{ "passwordManager" | i18n }}</h3>
|
||||
<app-adjust-subscription
|
||||
[seatPrice]="seatPrice"
|
||||
[organizationId]="organizationId"
|
||||
[interval]="billingInterval"
|
||||
[currentSeatCount]="seats"
|
||||
[maxAutoscaleSeats]="maxAutoscaleSeats"
|
||||
(onAdjusted)="subscriptionAdjusted()"
|
||||
>
|
||||
</app-adjust-subscription>
|
||||
</ng-container>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
type="button"
|
||||
[bitAction]="removeSponsorship"
|
||||
*ngIf="isSponsoredSubscription"
|
||||
>
|
||||
{{ "removeSponsorship" | i18n }}
|
||||
</button>
|
||||
<h4 bitTypography="h4" class="tw-mt-9">{{ "storage" | i18n }}</h4>
|
||||
<p bitTypography="body1">
|
||||
{{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }}
|
||||
</p>
|
||||
<bit-progress [barWidth]="storagePercentage" bgColor="success"></bit-progress>
|
||||
<ng-container *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
|
||||
<div class="tw-mt-3">
|
||||
<div class="tw-flex tw-space-x-2" *ngIf="!showAdjustStorage">
|
||||
<button bitButton buttonType="secondary" type="button" (click)="adjustStorage(true)">
|
||||
{{ "addStorage" | i18n }}
|
||||
</button>
|
||||
<button bitButton buttonType="secondary" type="button" (click)="adjustStorage(false)">
|
||||
{{ "removeStorage" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<app-adjust-storage
|
||||
[storageGbPrice]="storageGbPrice"
|
||||
[add]="adjustStorageAdd"
|
||||
[organizationId]="organizationId"
|
||||
[interval]="billingInterval"
|
||||
(onAdjusted)="closeStorage(true)"
|
||||
(onCanceled)="closeStorage(false)"
|
||||
*ngIf="showAdjustStorage"
|
||||
></app-adjust-storage>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showAdjustSecretsManager">
|
||||
<h3 bitTypography="h3" class="tw-mt-9">{{ "secretsManager" | i18n }}</h3>
|
||||
<app-sm-adjust-subscription
|
||||
[organizationId]="organizationId"
|
||||
[options]="smOptions"
|
||||
(onAdjusted)="subscriptionAdjusted()"
|
||||
></app-sm-adjust-subscription>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<h2 bitTypography="h2" class="tw-mt-7">{{ "selfHostingTitle" | i18n }}</h2>
|
||||
<p bitTypography="body1">
|
||||
{{ "additionalOptionsDesc" | i18n }}
|
||||
{{ "selfHostingEnterpriseOrganizationSectionCopy" | i18n }}
|
||||
</p>
|
||||
<div class="tw-flex tw-space-x-2">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
[bitAction]="cancelWithWarning"
|
||||
buttonType="secondary"
|
||||
type="button"
|
||||
*ngIf="
|
||||
subscription &&
|
||||
!subscription.cancelled &&
|
||||
!subscriptionMarkedForCancel &&
|
||||
!(presentUserWithOffboardingSurvey$ | async)
|
||||
"
|
||||
(click)="downloadLicense()"
|
||||
*ngIf="canDownloadLicense"
|
||||
[disabled]="showDownloadLicense"
|
||||
>
|
||||
{{ "cancelSubscription" | i18n }}
|
||||
{{ "downloadLicense" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
(click)="cancelWithOffboardingSurvey()"
|
||||
buttonType="secondary"
|
||||
type="button"
|
||||
*ngIf="
|
||||
subscription &&
|
||||
!subscription.cancelled &&
|
||||
!subscriptionMarkedForCancel &&
|
||||
(presentUserWithOffboardingSurvey$ | async)
|
||||
"
|
||||
(click)="manageBillingSync()"
|
||||
*ngIf="canManageBillingSync"
|
||||
>
|
||||
{{ "cancelSubscription" | i18n }}
|
||||
{{ (hasBillingSyncToken ? "manageBillingSync" : "setUpBillingSync") | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="tw-mt-3" *ngIf="showDownloadLicense">
|
||||
<app-download-license
|
||||
[organizationId]="organizationId"
|
||||
(onDownloaded)="closeDownloadLicense()"
|
||||
(onCanceled)="closeDownloadLicense()"
|
||||
></app-download-license>
|
||||
</div>
|
||||
<ng-container *ngIf="userOrg.canEditSubscription">
|
||||
<h2 bitTypography="h2" class="tw-mt-7">{{ "additionalOptions" | i18n }}</h2>
|
||||
<p bitTypography="body1">
|
||||
{{ "additionalOptionsDesc" | i18n }}
|
||||
</p>
|
||||
<div class="tw-flex tw-space-x-2">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
[bitAction]="cancelWithWarning"
|
||||
type="button"
|
||||
*ngIf="
|
||||
subscription &&
|
||||
!subscription.cancelled &&
|
||||
!subscriptionMarkedForCancel &&
|
||||
!(presentUserWithOffboardingSurvey$ | async)
|
||||
"
|
||||
>
|
||||
{{ "cancelSubscription" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
(click)="cancelWithOffboardingSurvey()"
|
||||
type="button"
|
||||
*ngIf="
|
||||
subscription &&
|
||||
!subscription.cancelled &&
|
||||
!subscriptionMarkedForCancel &&
|
||||
(presentUserWithOffboardingSurvey$ | async)
|
||||
"
|
||||
>
|
||||
{{ "cancelSubscription" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</bit-container>
|
||||
|
||||
@ -1,137 +1,131 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{ "subscription" | i18n }}
|
||||
<small *ngIf="firstLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<app-header></app-header>
|
||||
|
||||
<ng-container *ngIf="!firstLoaded && loading">
|
||||
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<bit-container>
|
||||
<ng-container *ngIf="!firstLoaded && loading">
|
||||
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
|
||||
<app-org-subscription-hidden
|
||||
*ngIf="firstLoaded && !userOrg.canViewSubscription"
|
||||
[providerName]="userOrg.providerName"
|
||||
></app-org-subscription-hidden>
|
||||
<app-org-subscription-hidden
|
||||
*ngIf="firstLoaded && !userOrg.canViewSubscription"
|
||||
[providerName]="userOrg.providerName"
|
||||
></app-org-subscription-hidden>
|
||||
|
||||
<ng-container *ngIf="subscription && firstLoaded">
|
||||
<dl>
|
||||
<dt>{{ "billingPlan" | i18n }}</dt>
|
||||
<dd>{{ subscription.planName }}</dd>
|
||||
<ng-container *ngIf="billingSyncSetUp">
|
||||
<dt>{{ "lastLicenseSync" | i18n }}</dt>
|
||||
<dd>
|
||||
{{ lastLicenseSync != null ? (lastLicenseSync | date: "medium") : ("never" | i18n) }}
|
||||
</dd>
|
||||
</ng-container>
|
||||
<dt>
|
||||
<span [ngClass]="{ 'tw-text-danger': showAsExpired }">{{
|
||||
"subscriptionExpiration" | i18n
|
||||
}}</span>
|
||||
<a
|
||||
href="https://bitwarden.com/help/licensing-on-premise/#update-organization-license"
|
||||
target="_blank"
|
||||
[appA11yTitle]="'licensePaidFeaturesHelp' | i18n"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "licensePaidFeaturesHelp" | i18n }}</span>
|
||||
</a>
|
||||
</dt>
|
||||
<dd *ngIf="subscription.hasExpiration" [ngClass]="{ 'tw-text-danger': showAsExpired }">
|
||||
{{
|
||||
(subscription.hasSeparateGracePeriod
|
||||
? subscription.expirationWithoutGracePeriod
|
||||
: subscription.expirationWithGracePeriod
|
||||
) | date: "mediumDate"
|
||||
}}
|
||||
<div *ngIf="subscription.hasSeparateGracePeriod" class="tw-text-muted">
|
||||
<ng-container *ngIf="subscription && firstLoaded">
|
||||
<dl>
|
||||
<dt>{{ "billingPlan" | i18n }}</dt>
|
||||
<dd>{{ subscription.planName }}</dd>
|
||||
<ng-container *ngIf="billingSyncSetUp">
|
||||
<dt>{{ "lastLicenseSync" | i18n }}</dt>
|
||||
<dd>
|
||||
{{ lastLicenseSync != null ? (lastLicenseSync | date: "medium") : ("never" | i18n) }}
|
||||
</dd>
|
||||
</ng-container>
|
||||
<dt>
|
||||
<span [ngClass]="{ 'tw-text-danger': showAsExpired }">{{
|
||||
"subscriptionExpiration" | i18n
|
||||
}}</span>
|
||||
<a
|
||||
href="https://bitwarden.com/help/licensing-on-premise/#update-organization-license"
|
||||
target="_blank"
|
||||
[appA11yTitle]="'licensePaidFeaturesHelp' | i18n"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "licensePaidFeaturesHelp" | i18n }}</span>
|
||||
</a>
|
||||
</dt>
|
||||
<dd *ngIf="subscription.hasExpiration" [ngClass]="{ 'tw-text-danger': showAsExpired }">
|
||||
{{
|
||||
"selfHostGracePeriodHelp"
|
||||
| i18n: (subscription.expirationWithGracePeriod | date: "mediumDate")
|
||||
(subscription.hasSeparateGracePeriod
|
||||
? subscription.expirationWithoutGracePeriod
|
||||
: subscription.expirationWithGracePeriod
|
||||
) | date: "mediumDate"
|
||||
}}
|
||||
</div>
|
||||
</dd>
|
||||
<dd *ngIf="!subscription.hasExpiration">{{ "neverExpires" | i18n }}</dd>
|
||||
</dl>
|
||||
<div *ngIf="subscription.hasSeparateGracePeriod" class="tw-text-muted">
|
||||
{{
|
||||
"selfHostGracePeriodHelp"
|
||||
| i18n: (subscription.expirationWithGracePeriod | date: "mediumDate")
|
||||
}}
|
||||
</div>
|
||||
</dd>
|
||||
<dd *ngIf="!subscription.hasExpiration">{{ "neverExpires" | i18n }}</dd>
|
||||
</dl>
|
||||
|
||||
<a
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
href="{{ this.cloudWebVaultUrl }}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{{ "launchCloudSubscription" | i18n }}
|
||||
</a>
|
||||
<form [formGroup]="form">
|
||||
<bit-radio-group formControlName="updateMethod">
|
||||
<h2 class="mt-5">
|
||||
{{ "licenseAndBillingManagement" | i18n }}
|
||||
</h2>
|
||||
<bit-radio-button
|
||||
id="automatic-sync"
|
||||
[value]="licenseOptions.SYNC"
|
||||
[disabled]="disableLicenseSyncControl"
|
||||
class="tw-block"
|
||||
>
|
||||
<bit-label
|
||||
>{{ "automaticSync" | i18n }}
|
||||
<a
|
||||
href="https://bitwarden.com/help/families-for-enterprise-self-hosted/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
<a
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
href="{{ this.cloudWebVaultUrl }}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{{ "launchCloudSubscription" | i18n }}
|
||||
</a>
|
||||
<form [formGroup]="form">
|
||||
<bit-radio-group formControlName="updateMethod">
|
||||
<h2 class="mt-5">
|
||||
{{ "licenseAndBillingManagement" | i18n }}
|
||||
</h2>
|
||||
<bit-radio-button
|
||||
id="automatic-sync"
|
||||
[value]="licenseOptions.SYNC"
|
||||
[disabled]="disableLicenseSyncControl"
|
||||
class="tw-block"
|
||||
>
|
||||
<bit-label
|
||||
>{{ "automaticSync" | i18n }}
|
||||
<a
|
||||
href="https://bitwarden.com/help/families-for-enterprise-self-hosted/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "billingSyncHelp" | i18n }}</span>
|
||||
</a>
|
||||
</bit-label>
|
||||
<bit-hint>
|
||||
{{ "billingSyncDesc" | i18n }}
|
||||
</bit-hint>
|
||||
</bit-radio-button>
|
||||
<ng-container *ngIf="updateMethod === licenseOptions.SYNC">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
type="button"
|
||||
(click)="manageBillingSyncSelfHosted()"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "billingSyncHelp" | i18n }}</span>
|
||||
</a>
|
||||
</bit-label>
|
||||
<bit-hint>
|
||||
{{ "billingSyncDesc" | i18n }}
|
||||
</bit-hint>
|
||||
</bit-radio-button>
|
||||
<ng-container *ngIf="updateMethod === licenseOptions.SYNC">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
type="button"
|
||||
(click)="manageBillingSyncSelfHosted()"
|
||||
>
|
||||
{{ "manageBillingSync" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
[bitAction]="syncLicense"
|
||||
[disabled]="!billingSyncEnabled"
|
||||
>
|
||||
{{ "syncLicense" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
{{ "manageBillingSync" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
[bitAction]="syncLicense"
|
||||
[disabled]="!billingSyncEnabled"
|
||||
>
|
||||
{{ "syncLicense" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<bit-radio-button id="manual-upload" [value]="licenseOptions.UPLOAD" class="tw-mt-6 tw-block">
|
||||
<bit-label>{{ "manualUpload" | i18n }}</bit-label>
|
||||
<bit-hint>
|
||||
{{ "manualUploadDesc" | i18n }}
|
||||
</bit-hint>
|
||||
</bit-radio-button>
|
||||
<ng-container *ngIf="updateMethod === licenseOptions.UPLOAD">
|
||||
<h3 class="tw-font-semibold">{{ "uploadLicense" | i18n }}</h3>
|
||||
<app-update-license
|
||||
[organizationId]="organizationId"
|
||||
[showCancel]="false"
|
||||
(onUpdated)="licenseUploaded()"
|
||||
></app-update-license>
|
||||
</ng-container>
|
||||
</bit-radio-group>
|
||||
</form>
|
||||
</ng-container>
|
||||
<bit-radio-button
|
||||
id="manual-upload"
|
||||
[value]="licenseOptions.UPLOAD"
|
||||
class="tw-mt-6 tw-block"
|
||||
>
|
||||
<bit-label>{{ "manualUpload" | i18n }}</bit-label>
|
||||
<bit-hint>
|
||||
{{ "manualUploadDesc" | i18n }}
|
||||
</bit-hint>
|
||||
</bit-radio-button>
|
||||
<ng-container *ngIf="updateMethod === licenseOptions.UPLOAD">
|
||||
<h3 class="tw-font-semibold">{{ "uploadLicense" | i18n }}</h3>
|
||||
<app-update-license
|
||||
[organizationId]="organizationId"
|
||||
[showCancel]="false"
|
||||
(onUpdated)="licenseUploaded()"
|
||||
></app-update-license>
|
||||
</ng-container>
|
||||
</bit-radio-group>
|
||||
</form>
|
||||
</ng-container>
|
||||
</bit-container>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
import { AddCreditComponent } from "./add-credit.component";
|
||||
@ -14,7 +15,7 @@ import { TaxInfoComponent } from "./tax-info.component";
|
||||
import { UpdateLicenseComponent } from "./update-license.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, PaymentComponent, TaxInfoComponent],
|
||||
imports: [SharedModule, PaymentComponent, TaxInfoComponent, HeaderModule],
|
||||
declarations: [
|
||||
AddCreditComponent,
|
||||
AdjustPaymentComponent,
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
<div class="d-flex" [ngClass]="headerClass">
|
||||
<h1>
|
||||
{{ "paymentMethod" | i18n }}
|
||||
</h1>
|
||||
<app-header *ngIf="organizationId">
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
@ -14,133 +11,145 @@
|
||||
<i class="bwi bwi-refresh bwi-fw" [ngClass]="{ 'bwi-spin': loading }" aria-hidden="true"></i>
|
||||
{{ "refresh" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<ng-container *ngIf="!firstLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="billing">
|
||||
<h2>{{ (isCreditBalance ? "accountCredit" : "accountBalance") | i18n }}</h2>
|
||||
<p class="text-lg">
|
||||
<strong>{{ creditOrBalance | currency: "$" }}</strong>
|
||||
</p>
|
||||
<p>{{ "creditAppliedDesc" | i18n }}</p>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="addCredit()"
|
||||
*ngIf="!showAddCredit"
|
||||
>
|
||||
{{ "addCredit" | i18n }}
|
||||
</button>
|
||||
<app-add-credit
|
||||
[organizationId]="organizationId"
|
||||
(onAdded)="closeAddCredit(true)"
|
||||
(onCanceled)="closeAddCredit(false)"
|
||||
*ngIf="showAddCredit"
|
||||
>
|
||||
</app-add-credit>
|
||||
<h2 class="spaced-header">{{ "paymentMethod" | i18n }}</h2>
|
||||
<p *ngIf="!paymentSource">{{ "noPaymentMethod" | i18n }}</p>
|
||||
<ng-container *ngIf="paymentSource">
|
||||
<app-callout
|
||||
type="warning"
|
||||
title="{{ 'verifyBankAccount' | i18n }}"
|
||||
*ngIf="
|
||||
forOrganization &&
|
||||
paymentSource.type === paymentMethodType.BankAccount &&
|
||||
paymentSource.needsVerification
|
||||
"
|
||||
</app-header>
|
||||
|
||||
<bit-container>
|
||||
<div class="tabbed-header" *ngIf="!organizationId">
|
||||
<!-- TODO: Organization and individual should use different "page" components -->
|
||||
<h1>{{ "paymentMethod" | i18n }}</h1>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!firstLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="billing">
|
||||
<h2>{{ (isCreditBalance ? "accountCredit" : "accountBalance") | i18n }}</h2>
|
||||
<p class="text-lg">
|
||||
<strong>{{ creditOrBalance | currency: "$" }}</strong>
|
||||
</p>
|
||||
<p>{{ "creditAppliedDesc" | i18n }}</p>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="addCredit()"
|
||||
*ngIf="!showAddCredit"
|
||||
>
|
||||
<p>{{ "verifyBankAccountDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }}</p>
|
||||
{{ "addCredit" | i18n }}
|
||||
</button>
|
||||
<app-add-credit
|
||||
[organizationId]="organizationId"
|
||||
(onAdded)="closeAddCredit(true)"
|
||||
(onCanceled)="closeAddCredit(false)"
|
||||
*ngIf="showAddCredit"
|
||||
>
|
||||
</app-add-credit>
|
||||
<h2 class="spaced-header">{{ "paymentMethod" | i18n }}</h2>
|
||||
<p *ngIf="!paymentSource">{{ "noPaymentMethod" | i18n }}</p>
|
||||
<ng-container *ngIf="paymentSource">
|
||||
<app-callout
|
||||
type="warning"
|
||||
title="{{ 'verifyBankAccount' | i18n }}"
|
||||
*ngIf="
|
||||
forOrganization &&
|
||||
paymentSource.type === paymentMethodType.BankAccount &&
|
||||
paymentSource.needsVerification
|
||||
"
|
||||
>
|
||||
<p>{{ "verifyBankAccountDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }}</p>
|
||||
<form
|
||||
#verifyForm
|
||||
class="form-inline"
|
||||
(ngSubmit)="verifyBank()"
|
||||
[formGroup]="verifyBankForm"
|
||||
[appApiAction]="verifyBankPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<bit-form-field class="tw-mr-2 tw-w-40">
|
||||
<bit-label>{{ "amountX" | i18n: "1" }}</bit-label>
|
||||
<input bitInput type="number" step="1" placeholder="xx" formControlName="amount1" />
|
||||
<span bitPrefix>$0.</span>
|
||||
</bit-form-field>
|
||||
<bit-form-field class="tw-mr-2 tw-w-40">
|
||||
<bit-label>{{ "amountX" | i18n: "2" }}</bit-label>
|
||||
<input bitInput type="number" step="1" placeholder="xx" formControlName="amount2" />
|
||||
<span bitPrefix>$0.</span>
|
||||
</bit-form-field>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
class="btn-submit"
|
||||
[disabled]="verifyForm.loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{{ "verifyBankAccount" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</app-callout>
|
||||
<p>
|
||||
<i class="bwi bwi-fw" [ngClass]="paymentSourceClasses"></i>
|
||||
{{ paymentSource.description }}
|
||||
</p>
|
||||
</ng-container>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="changePayment()"
|
||||
*ngIf="!showAdjustPayment"
|
||||
>
|
||||
{{ (paymentSource ? "changePaymentMethod" : "addPaymentMethod") | i18n }}
|
||||
</button>
|
||||
<app-adjust-payment
|
||||
[organizationId]="organizationId"
|
||||
[currentType]="paymentSource != null ? paymentSource.type : null"
|
||||
(onAdjusted)="closePayment(true)"
|
||||
(onCanceled)="closePayment(false)"
|
||||
*ngIf="showAdjustPayment"
|
||||
>
|
||||
</app-adjust-payment>
|
||||
<p *ngIf="isUnpaid">{{ "paymentChargedWithUnpaidSubscription" | i18n }}</p>
|
||||
<ng-container *ngIf="forOrganization">
|
||||
<h2 class="spaced-header">{{ "taxInformation" | i18n }}</h2>
|
||||
<p>{{ "taxInformationDesc" | i18n }}</p>
|
||||
<div *ngIf="!org || loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<form
|
||||
#verifyForm
|
||||
class="form-inline"
|
||||
(ngSubmit)="verifyBank()"
|
||||
[formGroup]="verifyBankForm"
|
||||
[appApiAction]="verifyBankPromise"
|
||||
*ngIf="org && !loading"
|
||||
#formTax
|
||||
(ngSubmit)="submitTaxInfo()"
|
||||
[appApiAction]="taxFormPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<bit-form-field class="tw-mr-2 tw-w-40">
|
||||
<bit-label>{{ "amountX" | i18n: "1" }}</bit-label>
|
||||
<input bitInput type="number" step="1" placeholder="xx" formControlName="amount1" />
|
||||
<span bitPrefix>$0.</span>
|
||||
</bit-form-field>
|
||||
<bit-form-field class="tw-mr-2 tw-w-40">
|
||||
<bit-label>{{ "amountX" | i18n: "2" }}</bit-label>
|
||||
<input bitInput type="number" step="1" placeholder="xx" formControlName="amount2" />
|
||||
<span bitPrefix>$0.</span>
|
||||
</bit-form-field>
|
||||
<app-tax-info></app-tax-info>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
class="btn-submit"
|
||||
[disabled]="verifyForm.loading"
|
||||
[disabled]="formTax.loading"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "verifyBankAccount" | i18n }}</span>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</app-callout>
|
||||
<p>
|
||||
<i class="bwi bwi-fw" [ngClass]="paymentSourceClasses"></i>
|
||||
{{ paymentSource.description }}
|
||||
</p>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="changePayment()"
|
||||
*ngIf="!showAdjustPayment"
|
||||
>
|
||||
{{ (paymentSource ? "changePaymentMethod" : "addPaymentMethod") | i18n }}
|
||||
</button>
|
||||
<app-adjust-payment
|
||||
[organizationId]="organizationId"
|
||||
[currentType]="paymentSource != null ? paymentSource.type : null"
|
||||
(onAdjusted)="closePayment(true)"
|
||||
(onCanceled)="closePayment(false)"
|
||||
*ngIf="showAdjustPayment"
|
||||
>
|
||||
</app-adjust-payment>
|
||||
<p *ngIf="isUnpaid">{{ "paymentChargedWithUnpaidSubscription" | i18n }}</p>
|
||||
<ng-container *ngIf="forOrganization">
|
||||
<h2 class="spaced-header">{{ "taxInformation" | i18n }}</h2>
|
||||
<p>{{ "taxInformationDesc" | i18n }}</p>
|
||||
<div *ngIf="!org || loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<form
|
||||
*ngIf="org && !loading"
|
||||
#formTax
|
||||
(ngSubmit)="submitTaxInfo()"
|
||||
[appApiAction]="taxFormPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<app-tax-info></app-tax-info>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
class="btn-submit"
|
||||
[disabled]="formTax.loading"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</bit-container>
|
||||
|
||||
@ -3,8 +3,8 @@ import { combineLatest, Observable, switchMap } from "rxjs";
|
||||
|
||||
import { OrganizationApiServiceAbstraction as OrganizationApiService } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import {
|
||||
OrganizationService,
|
||||
canAccessAdmin,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { BillingBannerServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-banner.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
<div class="container footer text-muted">
|
||||
<div class="row">
|
||||
<div class="col">© {{ year }} Bitwarden Inc.</div>
|
||||
<div class="col text-center"></div>
|
||||
<div class="col text-right">
|
||||
{{ "versionNumber" | i18n: version }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,19 +0,0 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-footer",
|
||||
templateUrl: "footer.component.html",
|
||||
})
|
||||
export class FooterComponent implements OnInit {
|
||||
version: string;
|
||||
year = "2015";
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.year = new Date().getFullYear().toString();
|
||||
this.version = await this.platformUtilsService.getApplicationVersion();
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { BannerModule } from "@bitwarden/components";
|
||||
|
||||
import { DynamicAvatarComponent } from "../../components/dynamic-avatar.component";
|
||||
import { SharedModule } from "../../shared";
|
||||
import { ProductSwitcherModule } from "../product-switcher/product-switcher.module";
|
||||
@ -7,7 +9,7 @@ import { ProductSwitcherModule } from "../product-switcher/product-switcher.modu
|
||||
import { WebHeaderComponent } from "./web-header.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, DynamicAvatarComponent, ProductSwitcherModule],
|
||||
imports: [SharedModule, DynamicAvatarComponent, ProductSwitcherModule, BannerModule],
|
||||
declarations: [WebHeaderComponent],
|
||||
exports: [WebHeaderComponent],
|
||||
})
|
||||
|
||||
@ -1,3 +1,18 @@
|
||||
<bit-banner
|
||||
class="-tw-m-6 tw-flex tw-flex-col tw-pb-6"
|
||||
(onClose)="webLayoutMigrationBannerService.hideBanner()"
|
||||
*ngIf="webLayoutMigrationBannerService.showBanner$ | async"
|
||||
>
|
||||
{{ "newWebApp" | i18n }}
|
||||
<a
|
||||
href="https://bitwarden.com/blog/bitwarden-design-updating-the-navigation-in-the-web-app"
|
||||
bitLink
|
||||
linkType="contrast"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>{{ "releaseBlog" | i18n }}</a
|
||||
>
|
||||
</bit-banner>
|
||||
<header
|
||||
*ngIf="routeData$ | async as routeData"
|
||||
class="-tw-m-6 tw-mb-3 tw-flex tw-flex-col tw-p-6"
|
||||
|
||||
@ -9,6 +9,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { AccountProfile } from "@bitwarden/common/platform/models/domain/account";
|
||||
|
||||
import { WebLayoutMigrationBannerService } from "./web-layout-migration-banner.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-header",
|
||||
templateUrl: "./web-header.component.html",
|
||||
@ -36,6 +38,7 @@ export class WebHeaderComponent {
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
private messagingService: MessagingService,
|
||||
protected webLayoutMigrationBannerService: WebLayoutMigrationBannerService,
|
||||
) {
|
||||
this.routeData$ = this.route.data.pipe(
|
||||
map((params) => {
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Injectable, importProvidersFrom } from "@angular/core";
|
||||
import { Component, importProvidersFrom, Injectable, Input } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import {
|
||||
Meta,
|
||||
Story,
|
||||
moduleMetadata,
|
||||
applicationConfig,
|
||||
componentWrapperDecorator,
|
||||
Meta,
|
||||
moduleMetadata,
|
||||
Story,
|
||||
} from "@storybook/angular";
|
||||
import { BehaviorSubject, combineLatest, map } from "rxjs";
|
||||
import { BehaviorSubject, combineLatest, map, of } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
@ -22,16 +22,19 @@ import {
|
||||
ButtonModule,
|
||||
IconButtonModule,
|
||||
IconModule,
|
||||
InputModule,
|
||||
MenuModule,
|
||||
NavigationModule,
|
||||
TabsModule,
|
||||
TypographyModule,
|
||||
InputModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { DynamicAvatarComponent } from "../../components/dynamic-avatar.component";
|
||||
import { PreloadedEnglishI18nModule } from "../../core/tests";
|
||||
import { WebHeaderComponent } from "../header/web-header.component";
|
||||
|
||||
import { WebLayoutMigrationBannerService } from "./web-layout-migration-banner.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
@ -70,7 +73,7 @@ class MockProductSwitcher {}
|
||||
standalone: true,
|
||||
imports: [CommonModule, AvatarModule],
|
||||
})
|
||||
class MockDynamicAvatar {
|
||||
class MockDynamicAvatar implements Partial<DynamicAvatarComponent> {
|
||||
protected name$ = combineLatest([
|
||||
this.stateService.accounts$,
|
||||
this.stateService.activeAccount$,
|
||||
@ -79,6 +82,10 @@ class MockDynamicAvatar {
|
||||
([accounts, activeAccount]) => accounts[activeAccount as keyof typeof accounts].profile.name,
|
||||
),
|
||||
);
|
||||
|
||||
@Input()
|
||||
text: string;
|
||||
|
||||
constructor(private stateService: MockStateService) {}
|
||||
}
|
||||
|
||||
@ -107,6 +114,12 @@ export default {
|
||||
declarations: [WebHeaderComponent, MockProductSwitcher],
|
||||
providers: [
|
||||
{ provide: StateService, useClass: MockStateService },
|
||||
{
|
||||
provide: WebLayoutMigrationBannerService,
|
||||
useValue: {
|
||||
showBanner$: of(false),
|
||||
} as Partial<WebLayoutMigrationBannerService>,
|
||||
},
|
||||
{ provide: PlatformUtilsService, useClass: MockPlatformUtilsService },
|
||||
{ provide: VaultTimeoutSettingsService, useClass: MockVaultTimeoutService },
|
||||
{
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import {
|
||||
GlobalStateProvider,
|
||||
KeyDefinition,
|
||||
NEW_WEB_LAYOUT_BANNER_DISK,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
|
||||
const SHOW_BANNER_KEY = new KeyDefinition<boolean>(NEW_WEB_LAYOUT_BANNER_DISK, "showBanner", {
|
||||
deserializer: (b) => {
|
||||
if (b === null) {
|
||||
return true;
|
||||
}
|
||||
return b;
|
||||
},
|
||||
});
|
||||
|
||||
/** Displays a banner that introduces users to the new web vault layout. */
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class WebLayoutMigrationBannerService {
|
||||
private _showBannerState = this.globalStateProvider.get(SHOW_BANNER_KEY);
|
||||
showBanner$ = this._showBannerState.state$;
|
||||
|
||||
constructor(private globalStateProvider: GlobalStateProvider) {}
|
||||
|
||||
async hideBanner() {
|
||||
await this._showBannerState.update(() => false);
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
<nav class="navbar navbar-expand navbar-dark" [ngClass]="{ 'nav-background-alt': selfHosted }">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" routerLink="/" appA11yTitle="{{ 'bitWebVault' | i18n }}">
|
||||
<i class="bwi bwi-shield" aria-hidden="true"></i>
|
||||
</a>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/vault">{{ "vaults" | i18n }}</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/sends">{{ "send" | i18n }}</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
|
||||
</li>
|
||||
<li
|
||||
*ngIf="(organizations$ | async)?.length >= 1"
|
||||
class="nav-item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<a class="nav-link" [routerLink]="['/organizations', (organizations$ | async)[0].id]">{{
|
||||
"organizations" | i18n
|
||||
}}</a>
|
||||
</li>
|
||||
<ng-container *ngIf="providers.length >= 1">
|
||||
<li class="nav-item" routerLinkActive="active" *ngIf="providers.length == 1">
|
||||
<a class="nav-link" [routerLink]="['/providers', providers[0].id]">{{
|
||||
"provider" | i18n
|
||||
}}</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" *ngIf="providers.length > 1">
|
||||
<a class="nav-link" routerLink="/providers">{{ "provider" | i18n }}</a>
|
||||
</li>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</div>
|
||||
<product-switcher buttonType="light"></product-switcher>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
[bitMenuTriggerFor]="accountMenu"
|
||||
class="tw-border-0 tw-bg-transparent tw-text-alt2 tw-opacity-70 hover:tw-opacity-90"
|
||||
attr.aria-label="{{ 'accountLoggedInAsName' | i18n: name }}"
|
||||
>
|
||||
<dynamic-avatar
|
||||
[text]="name"
|
||||
[id]="userId"
|
||||
size="xsmall"
|
||||
aria-hidden="true"
|
||||
></dynamic-avatar>
|
||||
<i class="bwi bwi-caret-down bwi-sm" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu class="dropdown-menu" #accountMenu>
|
||||
<div class="tw-flex tw-min-w-[200px] tw-max-w-[300px] tw-flex-col">
|
||||
<div
|
||||
class="tw-flex tw-items-center tw-px-4 tw-py-1 tw-leading-tight tw-text-info"
|
||||
*ngIf="name"
|
||||
appStopProp
|
||||
>
|
||||
<dynamic-avatar [text]="name" [id]="userId" size="small"></dynamic-avatar>
|
||||
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
|
||||
<span>{{ "loggedInAs" | i18n }}</span>
|
||||
<small class="tw-block tw-overflow-hidden tw-whitespace-nowrap tw-text-muted">{{
|
||||
name
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
<a bitMenuItem routerLink="/settings/account">
|
||||
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
|
||||
{{ "accountSettings" | i18n }}
|
||||
</a>
|
||||
<a bitMenuItem href="https://bitwarden.com/help/" target="_blank" rel="noreferrer">
|
||||
<i class="bwi bwi-fw bwi-question-circle" aria-hidden="true"></i>
|
||||
{{ "getHelp" | i18n }}
|
||||
</a>
|
||||
<a bitMenuItem href="https://bitwarden.com/download/" target="_blank" rel="noreferrer">
|
||||
<i class="bwi bwi-fw bwi-download" aria-hidden="true"></i>
|
||||
{{ "getApps" | i18n }}
|
||||
</a>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
<button *ngIf="canLock$ | async" bitMenuItem type="button" (click)="lock()">
|
||||
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
|
||||
{{ "lockNow" | i18n }}
|
||||
</button>
|
||||
<button bitMenuItem type="button" (click)="logOut()">
|
||||
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
|
||||
{{ "logOut" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</bit-menu>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
@ -1,75 +0,0 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { map, Observable } from "rxjs";
|
||||
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import {
|
||||
canAccessAdmin,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
|
||||
@Component({
|
||||
selector: "app-navbar",
|
||||
templateUrl: "navbar.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class NavbarComponent implements OnInit {
|
||||
selfHosted = false;
|
||||
name: string;
|
||||
email: string;
|
||||
providers: Provider[] = [];
|
||||
userId: string;
|
||||
organizations$: Observable<Organization[]>;
|
||||
canLock$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private messagingService: MessagingService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private tokenService: TokenService,
|
||||
private providerService: ProviderService,
|
||||
private syncService: SyncService,
|
||||
private organizationService: OrganizationService,
|
||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
private i18nService: I18nService,
|
||||
) {
|
||||
this.selfHosted = this.platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.name = await this.tokenService.getName();
|
||||
this.email = await this.tokenService.getEmail();
|
||||
this.userId = await this.tokenService.getUserId();
|
||||
if (this.name == null || this.name.trim() === "") {
|
||||
this.name = this.email;
|
||||
}
|
||||
|
||||
// Ensure providers and organizations are loaded
|
||||
if ((await this.syncService.getLastSync()) == null) {
|
||||
await this.syncService.fullSync(false);
|
||||
}
|
||||
this.providers = await this.providerService.getAll();
|
||||
|
||||
this.organizations$ = this.organizationService.memberOrganizations$.pipe(
|
||||
canAccessAdmin(this.i18nService),
|
||||
);
|
||||
this.canLock$ = this.vaultTimeoutSettingsService
|
||||
.availableVaultTimeoutActions$()
|
||||
.pipe(map((actions) => actions.includes(VaultTimeoutAction.Lock)));
|
||||
}
|
||||
|
||||
lock() {
|
||||
this.messagingService.send("lockVault");
|
||||
}
|
||||
|
||||
logOut() {
|
||||
this.messagingService.send("logout");
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,18 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { combineLatest, map, Observable } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import type { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { NavigationModule } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
selector: "org-switcher",
|
||||
templateUrl: "org-switcher.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, JslibModule, NavigationModule],
|
||||
})
|
||||
export class OrgSwitcherComponent {
|
||||
protected organizations$: Observable<Organization[]> =
|
||||
5
apps/web/src/app/layouts/password-manager-logo.ts
Normal file
5
apps/web/src/app/layouts/password-manager-logo.ts
Normal file
File diff suppressed because one or more lines are too long
@ -1,8 +1,12 @@
|
||||
import { Component, ViewChild } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { combineLatest, map } from "rxjs";
|
||||
import { combineLatest, concatMap } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
canAccessOrgAdmin,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { MenuComponent } from "@bitwarden/components";
|
||||
|
||||
type ProductSwitcherItem = {
|
||||
@ -44,7 +48,7 @@ export class ProductSwitcherContentComponent {
|
||||
this.organizationService.organizations$,
|
||||
this.route.paramMap,
|
||||
]).pipe(
|
||||
map(([orgs, paramMap]) => {
|
||||
concatMap(async ([orgs, paramMap]) => {
|
||||
const routeOrg = orgs.find((o) => o.id === paramMap.get("organizationId"));
|
||||
// If the active route org doesn't have access to SM, find the first org that does.
|
||||
const smOrg =
|
||||
@ -52,17 +56,29 @@ export class ProductSwitcherContentComponent {
|
||||
? routeOrg
|
||||
: orgs.find((o) => o.canAccessSecretsManager && o.enabled == true);
|
||||
|
||||
// If the active route org doesn't have access to AC, find the first org that does.
|
||||
const acOrg =
|
||||
routeOrg != null && canAccessOrgAdmin(routeOrg) && routeOrg.enabled
|
||||
? routeOrg
|
||||
: orgs.find((o) => canAccessOrgAdmin(o) && o.enabled);
|
||||
|
||||
// TODO: This should be migrated to an Observable provided by the provider service and moved to the combineLatest above. See AC-2092.
|
||||
const providers = await this.providerService.getAll();
|
||||
|
||||
/**
|
||||
* We can update this to the "satisfies" type upon upgrading to TypeScript 4.9
|
||||
* https://devblogs.microsoft.com/typescript/announcing-typescript-4-9/#satisfies
|
||||
*/
|
||||
const products: Record<"pm" | "sm" | "orgs", ProductSwitcherItem> = {
|
||||
const products: Record<"pm" | "sm" | "ac" | "provider" | "orgs", ProductSwitcherItem> = {
|
||||
pm: {
|
||||
name: "Password Manager",
|
||||
icon: "bwi-lock",
|
||||
appRoute: "/vault",
|
||||
marketingRoute: "https://bitwarden.com/products/personal/",
|
||||
isActive: !this.router.url.includes("/sm/"),
|
||||
isActive:
|
||||
!this.router.url.includes("/sm/") &&
|
||||
!this.router.url.includes("/organizations/") &&
|
||||
!this.router.url.includes("/providers/"),
|
||||
},
|
||||
sm: {
|
||||
name: "Secrets Manager",
|
||||
@ -71,6 +87,19 @@ export class ProductSwitcherContentComponent {
|
||||
marketingRoute: "https://bitwarden.com/products/secrets-manager/",
|
||||
isActive: this.router.url.includes("/sm/"),
|
||||
},
|
||||
ac: {
|
||||
name: "Admin Console",
|
||||
icon: "bwi-business",
|
||||
appRoute: ["/organizations", acOrg?.id],
|
||||
marketingRoute: "https://bitwarden.com/products/business/",
|
||||
isActive: this.router.url.includes("/organizations/"),
|
||||
},
|
||||
provider: {
|
||||
name: "Provider Portal",
|
||||
icon: "bwi-provider",
|
||||
appRoute: ["/providers", providers[0]?.id],
|
||||
isActive: this.router.url.includes("/providers/"),
|
||||
},
|
||||
orgs: {
|
||||
name: "Organizations",
|
||||
icon: "bwi-business",
|
||||
@ -81,7 +110,9 @@ export class ProductSwitcherContentComponent {
|
||||
const bento: ProductSwitcherItem[] = [products.pm];
|
||||
const other: ProductSwitcherItem[] = [];
|
||||
|
||||
if (orgs.length === 0) {
|
||||
if (acOrg) {
|
||||
bento.push(products.ac);
|
||||
} else {
|
||||
other.push(products.orgs);
|
||||
}
|
||||
|
||||
@ -91,6 +122,10 @@ export class ProductSwitcherContentComponent {
|
||||
other.push(products.sm);
|
||||
}
|
||||
|
||||
if (providers.length > 0) {
|
||||
bento.push(products.provider);
|
||||
}
|
||||
|
||||
return {
|
||||
bento,
|
||||
other,
|
||||
@ -100,6 +135,7 @@ export class ProductSwitcherContentComponent {
|
||||
|
||||
constructor(
|
||||
private organizationService: OrganizationService,
|
||||
private providerService: ProviderService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
) {}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { Component, Directive, Input, importProvidersFrom } from "@angular/core";
|
||||
import { Component, Directive, importProvidersFrom, Input } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { applicationConfig, Meta, moduleMetadata, Story } from "@storybook/angular";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { IconButtonModule, LinkModule, MenuModule } from "@bitwarden/components";
|
||||
import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service";
|
||||
@ -26,6 +28,22 @@ class MockOrganizationService implements Partial<OrganizationService> {
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: "[mockProviders]",
|
||||
})
|
||||
class MockProviderService implements Partial<ProviderService> {
|
||||
private static _providers = new BehaviorSubject<Provider[]>([]);
|
||||
|
||||
async getAll() {
|
||||
return await firstValueFrom(MockProviderService._providers);
|
||||
}
|
||||
|
||||
@Input()
|
||||
set mockProviders(providers: Provider[]) {
|
||||
MockProviderService._providers.next(providers);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "story-layout",
|
||||
template: `<ng-content></ng-content>`,
|
||||
@ -46,6 +64,7 @@ export default {
|
||||
ProductSwitcherContentComponent,
|
||||
ProductSwitcherComponent,
|
||||
MockOrganizationService,
|
||||
MockProviderService,
|
||||
StoryLayoutComponent,
|
||||
StoryContentComponent,
|
||||
],
|
||||
@ -53,6 +72,8 @@ export default {
|
||||
providers: [
|
||||
{ provide: OrganizationService, useClass: MockOrganizationService },
|
||||
MockOrganizationService,
|
||||
{ provide: ProviderService, useClass: MockProviderService },
|
||||
MockProviderService,
|
||||
{
|
||||
provide: I18nService,
|
||||
useFactory: () => {
|
||||
@ -82,6 +103,10 @@ export default {
|
||||
path: "sm/:organizationId",
|
||||
component: StoryContentComponent,
|
||||
},
|
||||
{
|
||||
path: "providers/:providerId",
|
||||
component: StoryContentComponent,
|
||||
},
|
||||
{
|
||||
path: "vault",
|
||||
component: StoryContentComponent,
|
||||
@ -100,7 +125,7 @@ export default {
|
||||
const Template: Story = (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<router-outlet [mockOrgs]="mockOrgs"></router-outlet>
|
||||
<router-outlet [mockOrgs]="mockOrgs" [mockProviders]="mockProviders"></router-outlet>
|
||||
<div class="tw-flex tw-gap-[200px]">
|
||||
<div>
|
||||
<h1 class="tw-text-main tw-text-base tw-underline">Closed</h1>
|
||||
@ -119,17 +144,26 @@ const Template: Story = (args) => ({
|
||||
`,
|
||||
});
|
||||
|
||||
export const NoOrgs = Template.bind({});
|
||||
NoOrgs.args = {
|
||||
export const OnlyPM = Template.bind({});
|
||||
OnlyPM.args = {
|
||||
mockOrgs: [],
|
||||
mockProviders: [],
|
||||
};
|
||||
|
||||
export const OrgWithoutSecretsManager = Template.bind({});
|
||||
OrgWithoutSecretsManager.args = {
|
||||
mockOrgs: [{ id: "a" }],
|
||||
export const WithSM = Template.bind({});
|
||||
WithSM.args = {
|
||||
mockOrgs: [{ id: "org-a", canManageUsers: false, canAccessSecretsManager: true, enabled: true }],
|
||||
mockProviders: [],
|
||||
};
|
||||
|
||||
export const OrgWithSecretsManager = Template.bind({});
|
||||
OrgWithSecretsManager.args = {
|
||||
mockOrgs: [{ id: "b", canAccessSecretsManager: true, enabled: true }],
|
||||
export const WithSMAndAC = Template.bind({});
|
||||
WithSMAndAC.args = {
|
||||
mockOrgs: [{ id: "org-a", canManageUsers: true, canAccessSecretsManager: true, enabled: true }],
|
||||
mockProviders: [],
|
||||
};
|
||||
|
||||
export const WithAllOptions = Template.bind({});
|
||||
WithAllOptions.args = {
|
||||
mockOrgs: [{ id: "org-a", canManageUsers: true, canAccessSecretsManager: true, enabled: true }],
|
||||
mockProviders: [{ id: "provider-a" }],
|
||||
};
|
||||
|
||||
@ -1,4 +1,41 @@
|
||||
<app-navbar></app-navbar>
|
||||
<app-payment-method-banners *ngIf="false"></app-payment-method-banners>
|
||||
<router-outlet></router-outlet>
|
||||
<app-footer></app-footer>
|
||||
<bit-layout>
|
||||
<nav slot="sidebar">
|
||||
<a routerLink="." class="tw-m-5 tw-mt-7 tw-block">
|
||||
<bit-icon [icon]="logo"></bit-icon>
|
||||
</a>
|
||||
|
||||
<bit-nav-item icon="bwi-collection" [text]="'vaults' | i18n" route="vault"></bit-nav-item>
|
||||
<bit-nav-item icon="bwi-send" [text]="'send' | i18n" route="sends"></bit-nav-item>
|
||||
<bit-nav-group icon="bwi-wrench" [text]="'tools' | i18n" route="tools">
|
||||
<bit-nav-item [text]="'generator' | i18n" route="tools/generator"></bit-nav-item>
|
||||
<bit-nav-item [text]="'importData' | i18n" route="tools/import"></bit-nav-item>
|
||||
<bit-nav-item [text]="'exportVault' | i18n" route="tools/export"></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
<bit-nav-item icon="bwi-sliders" [text]="'reports' | i18n" route="reports"></bit-nav-item>
|
||||
<bit-nav-group icon="bwi-cog" [text]="'settings' | i18n" route="settings">
|
||||
<bit-nav-item [text]="'myAccount' | i18n" route="settings/account"></bit-nav-item>
|
||||
<bit-nav-item [text]="'security' | i18n" route="settings/security"></bit-nav-item>
|
||||
<bit-nav-item [text]="'preferences' | i18n" route="settings/preferences"></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'subscription' | i18n"
|
||||
route="settings/subscription"
|
||||
*ngIf="!hideSubscription"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item [text]="'domainRules' | i18n" route="settings/domain-rules"></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'emergencyAccess' | i18n"
|
||||
route="settings/emergency-access"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'sponsoredFamilies' | i18n"
|
||||
route="settings/sponsored-families"
|
||||
*ngIf="hasFamilySponsorshipAvailable"
|
||||
></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
</nav>
|
||||
<app-payment-method-banners
|
||||
*ngIf="false"
|
||||
class="-tw-m-6 tw-flex tw-flex-col tw-pb-6"
|
||||
></app-payment-method-banners>
|
||||
<router-outlet></router-outlet>
|
||||
</bit-layout>
|
||||
|
||||
@ -1,11 +1,86 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components";
|
||||
|
||||
import { PaymentMethodBannersComponent } from "../components/payment-method-banners/payment-method-banners.component";
|
||||
|
||||
import { PasswordManagerLogo } from "./password-manager-logo";
|
||||
|
||||
const BroadcasterSubscriptionId = "UserLayoutComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-user-layout",
|
||||
templateUrl: "user-layout.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
JslibModule,
|
||||
LayoutComponent,
|
||||
IconModule,
|
||||
NavigationModule,
|
||||
PaymentMethodBannersComponent,
|
||||
],
|
||||
})
|
||||
export class UserLayoutComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
export class UserLayoutComponent implements OnInit, OnDestroy {
|
||||
protected readonly logo = PasswordManagerLogo;
|
||||
hasFamilySponsorshipAvailable: boolean;
|
||||
hideSubscription: boolean;
|
||||
|
||||
constructor(
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationService: OrganizationService,
|
||||
private stateService: StateService,
|
||||
private apiService: ApiService,
|
||||
private syncService: SyncService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "purchasedPremium":
|
||||
await this.load();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await this.syncService.fullSync(false);
|
||||
await this.load();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
async load() {
|
||||
const premium = await this.stateService.getHasPremiumPersonally();
|
||||
const selfHosted = this.platformUtilsService.isSelfHost();
|
||||
|
||||
this.hasFamilySponsorshipAvailable = await this.organizationService.canManageSponsorships();
|
||||
const hasPremiumFromOrg = await this.stateService.getHasPremiumFromOrganization();
|
||||
let billing = null;
|
||||
if (!selfHosted) {
|
||||
// TODO: We should remove the need to call this!
|
||||
billing = await this.apiService.getUserBillingHistory();
|
||||
}
|
||||
this.hideSubscription = !premium && hasPremiumFromOrg && (selfHosted || billing?.hasNoHistory);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,12 +42,10 @@ import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
|
||||
import { UserLayoutComponent } from "./layouts/user-layout.component";
|
||||
import { DomainRulesComponent } from "./settings/domain-rules.component";
|
||||
import { PreferencesComponent } from "./settings/preferences.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { GeneratorComponent } from "./tools/generator.component";
|
||||
import { ReportsModule } from "./tools/reports";
|
||||
import { AccessComponent } from "./tools/send/access.component";
|
||||
import { SendComponent } from "./tools/send/send.component";
|
||||
import { ToolsComponent } from "./tools/tools.component";
|
||||
import { VaultModule } from "./vault/individual-vault/vault.module";
|
||||
|
||||
const routes: Routes = [
|
||||
@ -199,7 +197,7 @@ const routes: Routes = [
|
||||
path: "vault",
|
||||
loadChildren: () => VaultModule,
|
||||
},
|
||||
{ path: "sends", component: SendComponent, data: { title: "Send" } },
|
||||
{ path: "sends", component: SendComponent, data: { titleId: "send" } },
|
||||
{
|
||||
path: "create-organization",
|
||||
component: CreateOrganizationComponent,
|
||||
@ -207,7 +205,6 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: "settings",
|
||||
component: SettingsComponent,
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "account" },
|
||||
{ path: "account", component: AccountComponent, data: { titleId: "myAccount" } },
|
||||
@ -256,7 +253,6 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: "tools",
|
||||
component: ToolsComponent,
|
||||
canActivate: [AuthGuard],
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "generator" },
|
||||
|
||||
@ -1,108 +1,109 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "domainRules" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{ "domainRulesDesc" | i18n }}</p>
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<h2>{{ "customEqDomains" | i18n }}</h2>
|
||||
<p *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</p>
|
||||
<ng-container *ngIf="!loading">
|
||||
<div class="form-group d-flex" *ngFor="let d of custom; let i = index; trackBy: indexTrackBy">
|
||||
<div class="flex-fill">
|
||||
<label for="customDomain_{{ i }}" class="sr-only">{{
|
||||
"customDomainX" | i18n: i + 1
|
||||
}}</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
name="CustomDomain[{{ i }}]"
|
||||
id="customDomain_{{ i }}"
|
||||
[(ngModel)]="custom[i]"
|
||||
placeholder="{{ 'ex' | i18n }} google.com, gmail.com"
|
||||
required
|
||||
></textarea>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p>{{ "domainRulesDesc" | i18n }}</p>
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<h2>{{ "customEqDomains" | i18n }}</h2>
|
||||
<p *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</p>
|
||||
<ng-container *ngIf="!loading">
|
||||
<div class="form-group d-flex" *ngFor="let d of custom; let i = index; trackBy: indexTrackBy">
|
||||
<div class="flex-fill">
|
||||
<label for="customDomain_{{ i }}" class="sr-only">{{
|
||||
"customDomainX" | i18n: i + 1
|
||||
}}</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
name="CustomDomain[{{ i }}]"
|
||||
id="customDomain_{{ i }}"
|
||||
[(ngModel)]="custom[i]"
|
||||
placeholder="{{ 'ex' | i18n }} google.com, gmail.com"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link text-danger ml-2"
|
||||
(click)="remove(i)"
|
||||
appA11yTitle="{{ 'remove' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-minus-circle bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link text-danger ml-2"
|
||||
(click)="remove(i)"
|
||||
appA11yTitle="{{ 'remove' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-minus-circle bwi-lg" aria-hidden="true"></i>
|
||||
<button type="button" (click)="add()" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i> {{ "newCustomDomain" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" (click)="add()" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i> {{ "newCustomDomain" | i18n }}
|
||||
<small class="text-muted d-block mb-3">{{ "newCustomDomainDesc" | i18n }}</small>
|
||||
</ng-container>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<small class="text-muted d-block mb-3">{{ "newCustomDomainDesc" | i18n }}</small>
|
||||
</ng-container>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<h2 class="spaced-header">{{ "globalEqDomains" | i18n }}</h2>
|
||||
<p *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</p>
|
||||
<table class="table table-hover table-list" *ngIf="!loading && global.length > 0">
|
||||
<tbody>
|
||||
<tr *ngFor="let d of global">
|
||||
<td [ngClass]="{ 'table-list-strike': d.excluded }">{{ d.domains }}</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="toggleExcluded(d)"
|
||||
*ngIf="!d.excluded"
|
||||
<h2 class="spaced-header">{{ "globalEqDomains" | i18n }}</h2>
|
||||
<p *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</p>
|
||||
<table class="table table-hover table-list" *ngIf="!loading && global.length > 0">
|
||||
<tbody>
|
||||
<tr *ngFor="let d of global">
|
||||
<td [ngClass]="{ 'table-list-strike': d.excluded }">{{ d.domains }}</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "exclude" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="toggleExcluded(d)"
|
||||
*ngIf="d.excluded"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-plus" aria-hidden="true"></i>
|
||||
{{ "include" | i18n }}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="customize(d)">
|
||||
<i class="bwi bwi-fw bwi-cut" aria-hidden="true"></i>
|
||||
{{ "customize" | i18n }}
|
||||
</a>
|
||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="toggleExcluded(d)"
|
||||
*ngIf="!d.excluded"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "exclude" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="toggleExcluded(d)"
|
||||
*ngIf="d.excluded"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-plus" aria-hidden="true"></i>
|
||||
{{ "include" | i18n }}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="customize(d)">
|
||||
<i class="bwi bwi-fw bwi-cut" aria-hidden="true"></i>
|
||||
{{ "customize" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</bit-container>
|
||||
|
||||
@ -1,143 +1,129 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "preferences" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{ "preferencesDesc" | i18n }}</p>
|
||||
<form [formGroup]="form" (ngSubmit)="submit()" ngNativeValidate>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
|
||||
<span *ngIf="policy.timeout && policy.action">
|
||||
{{
|
||||
"vaultTimeoutPolicyWithActionInEffect"
|
||||
| i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
|
||||
}}
|
||||
</span>
|
||||
<span *ngIf="policy.timeout && !policy.action">
|
||||
{{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }}
|
||||
</span>
|
||||
<span *ngIf="!policy.timeout && policy.action">
|
||||
{{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
|
||||
</span>
|
||||
</app-callout>
|
||||
<app-vault-timeout-input
|
||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||
[formControl]="form.controls.vaultTimeout"
|
||||
ngDefaultControl
|
||||
>
|
||||
</app-vault-timeout-input>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="availableVaultTimeoutActions$ | async as availableVaultTimeoutActions">
|
||||
<div *ngIf="availableVaultTimeoutActions.length > 1" class="form-group">
|
||||
<label>{{ "vaultTimeoutAction" | i18n }}</label>
|
||||
<div
|
||||
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)"
|
||||
class="form-check form-check-block"
|
||||
>
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="vaultTimeoutAction"
|
||||
id="vaultTimeoutActionLock"
|
||||
value="{{ VaultTimeoutAction.Lock }}"
|
||||
formControlName="vaultTimeoutAction"
|
||||
/>
|
||||
<label class="form-check-label" for="vaultTimeoutActionLock">
|
||||
{{ "lock" | i18n }}
|
||||
<small>{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.LogOut)"
|
||||
class="form-check mt-2 form-check-block"
|
||||
>
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="vaultTimeoutAction"
|
||||
id="vaultTimeoutActionLogOut"
|
||||
value="{{ VaultTimeoutAction.LogOut }}"
|
||||
formControlName="vaultTimeoutAction"
|
||||
/>
|
||||
<label class="form-check-label" for="vaultTimeoutActionLogOut">
|
||||
{{ "logOut" | i18n }}
|
||||
<small>{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
|
||||
</label>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p>{{ "preferencesDesc" | i18n }}</p>
|
||||
<form [formGroup]="form" (ngSubmit)="submit()" ngNativeValidate>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
|
||||
<span *ngIf="policy.timeout && policy.action">
|
||||
{{
|
||||
"vaultTimeoutPolicyWithActionInEffect"
|
||||
| i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
|
||||
}}
|
||||
</span>
|
||||
<span *ngIf="policy.timeout && !policy.action">
|
||||
{{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }}
|
||||
</span>
|
||||
<span *ngIf="!policy.timeout && policy.action">
|
||||
{{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
|
||||
</span>
|
||||
</app-callout>
|
||||
<app-vault-timeout-input
|
||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||
[formControl]="form.controls.vaultTimeout"
|
||||
ngDefaultControl
|
||||
>
|
||||
</app-vault-timeout-input>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<div class="d-flex">
|
||||
<label for="locale">{{ "language" | i18n }}</label>
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://bitwarden.com/help/localization/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
<ng-container *ngIf="availableVaultTimeoutActions$ | async as availableVaultTimeoutActions">
|
||||
<div *ngIf="availableVaultTimeoutActions.length > 1" class="form-group">
|
||||
<label>{{ "vaultTimeoutAction" | i18n }}</label>
|
||||
<div
|
||||
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)"
|
||||
class="form-check form-check-block"
|
||||
>
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="vaultTimeoutAction"
|
||||
id="vaultTimeoutActionLock"
|
||||
value="{{ VaultTimeoutAction.Lock }}"
|
||||
formControlName="vaultTimeoutAction"
|
||||
/>
|
||||
<label class="form-check-label" for="vaultTimeoutActionLock">
|
||||
{{ "lock" | i18n }}
|
||||
<small>{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.LogOut)"
|
||||
class="form-check mt-2 form-check-block"
|
||||
>
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="vaultTimeoutAction"
|
||||
id="vaultTimeoutActionLogOut"
|
||||
value="{{ VaultTimeoutAction.LogOut }}"
|
||||
formControlName="vaultTimeoutAction"
|
||||
/>
|
||||
<label class="form-check-label" for="vaultTimeoutActionLogOut">
|
||||
{{ "logOut" | i18n }}
|
||||
<small>{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<div class="d-flex">
|
||||
<label for="locale">{{ "language" | i18n }}</label>
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://bitwarden.com/help/localization/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<select id="locale" name="Locale" formControlName="locale" class="form-control">
|
||||
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ "languageDesc" | i18n }}</small>
|
||||
</div>
|
||||
<select id="locale" name="Locale" formControlName="locale" class="form-control">
|
||||
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ "languageDesc" | i18n }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enableFavicons"
|
||||
name="enableFavicons"
|
||||
formControlName="enableFavicons"
|
||||
/>
|
||||
<label class="form-check-label" for="enableFavicons">
|
||||
{{ "enableFavicon" | i18n }}
|
||||
</label>
|
||||
<a
|
||||
href="https://bitwarden.com/help/website-icons/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enableFavicons"
|
||||
name="enableFavicons"
|
||||
formControlName="enableFavicons"
|
||||
/>
|
||||
<label class="form-check-label" for="enableFavicons">
|
||||
{{ "enableFavicon" | i18n }}
|
||||
</label>
|
||||
<a
|
||||
href="https://bitwarden.com/help/website-icons/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<small class="form-text text-muted">{{ "faviconDesc" | i18n }}</small>
|
||||
</div>
|
||||
<small class="form-text text-muted">{{ "faviconDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enableFullWidth"
|
||||
name="enableFullWidth"
|
||||
formControlName="enableFullWidth"
|
||||
/>
|
||||
<label class="form-check-label" for="enableFullWidth">
|
||||
{{ "enableFullWidth" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
<small class="form-text text-muted">{{ "enableFullWidthDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="theme">{{ "theme" | i18n }}</label>
|
||||
<select id="theme" name="theme" formControlName="theme" class="form-control">
|
||||
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ "themeDesc" | i18n }}</small>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="theme">{{ "theme" | i18n }}</label>
|
||||
<select id="theme" name="theme" formControlName="theme" class="form-control">
|
||||
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ "themeDesc" | i18n }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
</bit-container>
|
||||
|
||||
@ -42,7 +42,6 @@ export class PreferencesComponent implements OnInit {
|
||||
vaultTimeout: [null as number | null],
|
||||
vaultTimeoutAction: [VaultTimeoutAction.Lock],
|
||||
enableFavicons: true,
|
||||
enableFullWidth: false,
|
||||
theme: [ThemeType.Light],
|
||||
locale: [null as string | null],
|
||||
});
|
||||
@ -142,7 +141,6 @@ export class PreferencesComponent implements OnInit {
|
||||
this.vaultTimeoutSettingsService.vaultTimeoutAction$(),
|
||||
),
|
||||
enableFavicons: !(await this.settingsService.getDisableFavicon()),
|
||||
enableFullWidth: await this.stateService.getEnableFullWidth(),
|
||||
theme: await this.stateService.getTheme(),
|
||||
locale: (await this.stateService.getLocale()) ?? null,
|
||||
};
|
||||
@ -167,8 +165,6 @@ export class PreferencesComponent implements OnInit {
|
||||
values.vaultTimeoutAction,
|
||||
);
|
||||
await this.settingsService.setDisableFavicon(!values.enableFavicons);
|
||||
await this.stateService.setEnableFullWidth(values.enableFullWidth);
|
||||
this.messagingService.send("setFullWidth");
|
||||
if (values.theme !== this.startingTheme) {
|
||||
await this.themingService.updateConfiguredTheme(values.theme);
|
||||
this.startingTheme = values.theme;
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ "accountSettings" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="account" class="list-group-item" routerLinkActive="active">
|
||||
{{ "myAccount" | i18n }}
|
||||
</a>
|
||||
<a routerLink="security" class="list-group-item" routerLinkActive="active">
|
||||
{{ "security" | i18n }}
|
||||
</a>
|
||||
<a routerLink="preferences" class="list-group-item" routerLinkActive="active">
|
||||
{{ "preferences" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="subscription"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="!hideSubscription"
|
||||
>
|
||||
{{ "subscription" | i18n }}
|
||||
</a>
|
||||
<a routerLink="domain-rules" class="list-group-item" routerLinkActive="active">
|
||||
{{ "domainRules" | i18n }}
|
||||
</a>
|
||||
<a routerLink="emergency-access" class="list-group-item" routerLinkActive="active">
|
||||
{{ "emergencyAccess" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="sponsored-families"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="hasFamilySponsorshipAvailable"
|
||||
>
|
||||
{{ "sponsoredFamilies" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,8 +1,8 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth/angular";
|
||||
import { LayoutComponent, NavigationModule } from "@bitwarden/components";
|
||||
|
||||
import { OrganizationSwitcherComponent } from "../admin-console/components/organization-switcher.component";
|
||||
import { OrganizationLayoutComponent } from "../admin-console/organizations/layouts/organization-layout.component";
|
||||
import { EventsComponent as OrgEventsComponent } from "../admin-console/organizations/manage/events.component";
|
||||
import { UserConfirmComponent as OrgUserConfirmComponent } from "../admin-console/organizations/manage/user-confirm.component";
|
||||
@ -10,7 +10,6 @@ import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations
|
||||
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../admin-console/organizations/tools/exposed-passwords-report.component";
|
||||
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../admin-console/organizations/tools/inactive-two-factor-report.component";
|
||||
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../admin-console/organizations/tools/reused-passwords-report.component";
|
||||
import { ToolsComponent as OrgToolsComponent } from "../admin-console/organizations/tools/tools.component";
|
||||
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../admin-console/organizations/tools/unsecured-websites-report.component";
|
||||
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../admin-console/organizations/tools/weak-passwords-report.component";
|
||||
import { ProvidersComponent } from "../admin-console/providers/providers.component";
|
||||
@ -62,20 +61,17 @@ import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.comp
|
||||
import { DynamicAvatarComponent } from "../components/dynamic-avatar.component";
|
||||
import { PaymentMethodBannersComponent } from "../components/payment-method-banners/payment-method-banners.component";
|
||||
import { SelectableAvatarComponent } from "../components/selectable-avatar.component";
|
||||
import { FooterComponent } from "../layouts/footer.component";
|
||||
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
|
||||
import { NavbarComponent } from "../layouts/navbar.component";
|
||||
import { HeaderModule } from "../layouts/header/header.module";
|
||||
import { ProductSwitcherModule } from "../layouts/product-switcher/product-switcher.module";
|
||||
import { UserLayoutComponent } from "../layouts/user-layout.component";
|
||||
import { DomainRulesComponent } from "../settings/domain-rules.component";
|
||||
import { LowKdfComponent } from "../settings/low-kdf.component";
|
||||
import { PreferencesComponent } from "../settings/preferences.component";
|
||||
import { SettingsComponent } from "../settings/settings.component";
|
||||
import { VaultTimeoutInputComponent } from "../settings/vault-timeout-input.component";
|
||||
import { GeneratorComponent } from "../tools/generator.component";
|
||||
import { PasswordGeneratorHistoryComponent } from "../tools/password-generator-history.component";
|
||||
import { AddEditComponent as SendAddEditComponent } from "../tools/send/add-edit.component";
|
||||
import { ToolsComponent } from "../tools/tools.component";
|
||||
import { PremiumBadgeComponent } from "../vault/components/premium-badge.component";
|
||||
import { AddEditCustomFieldsComponent } from "../vault/individual-vault/add-edit-custom-fields.component";
|
||||
import { AddEditComponent } from "../vault/individual-vault/add-edit.component";
|
||||
@ -111,6 +107,11 @@ import { SharedModule } from "./shared.module";
|
||||
PasswordCalloutComponent,
|
||||
DangerZoneComponent,
|
||||
PaymentMethodBannersComponent,
|
||||
LayoutComponent,
|
||||
NavigationModule,
|
||||
HeaderModule,
|
||||
OrganizationLayoutComponent,
|
||||
UserLayoutComponent,
|
||||
],
|
||||
declarations: [
|
||||
AcceptFamilySponsorshipComponent,
|
||||
@ -134,21 +135,16 @@ import { SharedModule } from "./shared.module";
|
||||
EmergencyAccessViewComponent,
|
||||
EmergencyAddEditCipherComponent,
|
||||
FolderAddEditComponent,
|
||||
FooterComponent,
|
||||
FrontendLayoutComponent,
|
||||
HintComponent,
|
||||
LockComponent,
|
||||
NavbarComponent,
|
||||
OrganizationSwitcherComponent,
|
||||
OrgAddEditComponent,
|
||||
OrganizationLayoutComponent,
|
||||
OrgAttachmentsComponent,
|
||||
OrgCollectionsComponent,
|
||||
OrgEventsComponent,
|
||||
OrgExposedPasswordsReportComponent,
|
||||
OrgInactiveTwoFactorReportComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgToolsComponent,
|
||||
OrgUnsecuredWebsitesReportComponent,
|
||||
OrgUserConfirmComponent,
|
||||
OrgWeakPasswordsReportComponent,
|
||||
@ -168,12 +164,10 @@ import { SharedModule } from "./shared.module";
|
||||
SelectableAvatarComponent,
|
||||
SendAddEditComponent,
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponent,
|
||||
ToolsComponent,
|
||||
TwoFactorAuthenticatorComponent,
|
||||
TwoFactorComponent,
|
||||
TwoFactorDuoComponent,
|
||||
@ -186,7 +180,6 @@ import { SharedModule } from "./shared.module";
|
||||
TwoFactorYubiKeyComponent,
|
||||
UpdatePasswordComponent,
|
||||
UpdateTempPasswordComponent,
|
||||
UserLayoutComponent,
|
||||
VaultTimeoutInputComponent,
|
||||
VerifyEmailComponent,
|
||||
VerifyEmailTokenComponent,
|
||||
@ -217,12 +210,9 @@ import { SharedModule } from "./shared.module";
|
||||
EmergencyAccessViewComponent,
|
||||
EmergencyAddEditCipherComponent,
|
||||
FolderAddEditComponent,
|
||||
FooterComponent,
|
||||
FrontendLayoutComponent,
|
||||
HintComponent,
|
||||
LockComponent,
|
||||
NavbarComponent,
|
||||
OrganizationSwitcherComponent,
|
||||
OrgAddEditComponent,
|
||||
OrganizationLayoutComponent,
|
||||
OrgAttachmentsComponent,
|
||||
@ -231,7 +221,6 @@ import { SharedModule } from "./shared.module";
|
||||
OrgExposedPasswordsReportComponent,
|
||||
OrgInactiveTwoFactorReportComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgToolsComponent,
|
||||
OrgUnsecuredWebsitesReportComponent,
|
||||
OrgUserConfirmComponent,
|
||||
OrgWeakPasswordsReportComponent,
|
||||
@ -251,12 +240,10 @@ import { SharedModule } from "./shared.module";
|
||||
SelectableAvatarComponent,
|
||||
SendAddEditComponent,
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponent,
|
||||
ToolsComponent,
|
||||
TwoFactorAuthenticatorComponent,
|
||||
TwoFactorComponent,
|
||||
TwoFactorDuoComponent,
|
||||
@ -275,6 +262,7 @@ import { SharedModule } from "./shared.module";
|
||||
VerifyEmailTokenComponent,
|
||||
VerifyRecoverDeleteComponent,
|
||||
LowKdfComponent,
|
||||
HeaderModule,
|
||||
DangerZoneComponent,
|
||||
],
|
||||
})
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
CalloutModule,
|
||||
CheckboxModule,
|
||||
ColorPasswordModule,
|
||||
ContainerComponent,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
@ -63,6 +64,7 @@ import "./locales";
|
||||
CalloutModule,
|
||||
CheckboxModule,
|
||||
ColorPasswordModule,
|
||||
ContainerComponent,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
@ -98,6 +100,7 @@ import "./locales";
|
||||
CalloutModule,
|
||||
CheckboxModule,
|
||||
ColorPasswordModule,
|
||||
ContainerComponent,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
|
||||
@ -1,476 +1,479 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "generator" | i18n }}</h1>
|
||||
</div>
|
||||
<app-callout type="info" *ngIf="enforcedPasswordPolicyOptions?.inEffect() && type === 'password'">
|
||||
{{ "passwordGeneratorPolicyInEffect" | i18n }}
|
||||
</app-callout>
|
||||
<div class="card card-generated bg-light my-4">
|
||||
<div class="card-body">
|
||||
<bit-color-password
|
||||
[password]="type === 'password' ? password : username"
|
||||
[appCopyText]="type === 'password' ? password : username"
|
||||
></bit-color-password>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<app-callout type="info" *ngIf="enforcedPasswordPolicyOptions?.inEffect() && type === 'password'">
|
||||
{{ "passwordGeneratorPolicyInEffect" | i18n }}
|
||||
</app-callout>
|
||||
<div class="card card-generated bg-light my-4">
|
||||
<div class="card-body">
|
||||
<bit-color-password
|
||||
[password]="type === 'password' ? password : username"
|
||||
[appCopyText]="type === 'password' ? password : username"
|
||||
></bit-color-password>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" role="radiogroup" aria-labelledby="typeHeading">
|
||||
<label id="typeHeading" class="d-block">{{ "whatWouldYouLikeToGenerate" | i18n }}</label>
|
||||
<div class="form-check form-check-inline" *ngFor="let o of typeOptions">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
[(ngModel)]="type"
|
||||
name="Type"
|
||||
id="type_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="typeChanged()"
|
||||
[checked]="type === o.value"
|
||||
/>
|
||||
<label class="form-check-label" for="type_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="type === 'password'">
|
||||
<div aria-labelledby="passwordTypeHeading" class="form-group" role="radiogroup">
|
||||
<label id="passwordTypeHeading" class="d-block">{{ "passwordType" | i18n }}</label>
|
||||
<div class="form-check form-check-inline" *ngFor="let o of passTypeOptions">
|
||||
<div class="form-group" role="radiogroup" aria-labelledby="typeHeading">
|
||||
<label id="typeHeading" class="d-block">{{ "whatWouldYouLikeToGenerate" | i18n }}</label>
|
||||
<div class="form-check form-check-inline" *ngFor="let o of typeOptions">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
[(ngModel)]="passwordOptions.type"
|
||||
name="PasswordType"
|
||||
id="passwordType_{{ o.value }}"
|
||||
[(ngModel)]="type"
|
||||
name="Type"
|
||||
id="type_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="savePasswordOptions()"
|
||||
[checked]="passwordOptions.type === o.value"
|
||||
(change)="typeChanged()"
|
||||
[checked]="type === o.value"
|
||||
/>
|
||||
<label class="form-check-label" for="passwordType_{{ o.value }}">
|
||||
<label class="form-check-label" for="type_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="passwordOptions.type === 'passphrase'">
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<label for="num-words">{{ "numWords" | i18n }}</label>
|
||||
<ng-container *ngIf="type === 'password'">
|
||||
<div aria-labelledby="passwordTypeHeading" class="form-group" role="radiogroup">
|
||||
<label id="passwordTypeHeading" class="d-block">{{ "passwordType" | i18n }}</label>
|
||||
<div class="form-check form-check-inline" *ngFor="let o of passTypeOptions">
|
||||
<input
|
||||
id="num-words"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="3"
|
||||
max="20"
|
||||
[(ngModel)]="passwordOptions.numWords"
|
||||
(blur)="savePasswordOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
|
||||
<input
|
||||
id="word-separator"
|
||||
class="form-control"
|
||||
type="text"
|
||||
maxlength="1"
|
||||
[(ngModel)]="passwordOptions.wordSeparator"
|
||||
(blur)="savePasswordOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label class="d-block">{{ "options" | i18n }}</label>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="capitalize"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
type="radio"
|
||||
[(ngModel)]="passwordOptions.type"
|
||||
name="PasswordType"
|
||||
id="passwordType_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.capitalize"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
|
||||
[checked]="passwordOptions.type === o.value"
|
||||
/>
|
||||
<label for="capitalize" class="form-check-label">{{ "capitalize" | i18n }}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="include-number"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.includeNumber"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
|
||||
/>
|
||||
<label for="include-number" class="form-check-label">{{ "includeNumber" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="passwordOptions.type === 'password'">
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<label for="length">{{ "length" | i18n }}</label>
|
||||
<input
|
||||
id="length"
|
||||
class="form-control"
|
||||
type="number"
|
||||
[min]="passwordOptions.minLength"
|
||||
max="128"
|
||||
[(ngModel)]="passwordOptions.length"
|
||||
(blur)="savePasswordOptions()"
|
||||
(change)="lengthChanged()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="min-length">{{ "passwordMinLength" | i18n }}</label>
|
||||
<input
|
||||
id="min-length"
|
||||
class="form-control"
|
||||
type="text"
|
||||
readonly="true"
|
||||
[value]="passwordOptions.length"
|
||||
/>
|
||||
<span
|
||||
class="sr-only"
|
||||
attr.aria-label="{{ 'passwordMinLength' | i18n }}"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
{{ passwordOptionsMinLengthForReader$ | async }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="min-number">{{ "minNumbers" | i18n }}</label>
|
||||
<input
|
||||
id="min-number"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
[(ngModel)]="passwordOptions.minNumber"
|
||||
(input)="onPasswordOptionsMinNumberInput($event)"
|
||||
(change)="minNumberChanged()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="min-special">{{ "minSpecial" | i18n }}</label>
|
||||
<input
|
||||
id="min-special"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
[(ngModel)]="passwordOptions.minSpecial"
|
||||
(input)="onPasswordOptionsMinSpecialInput($event)"
|
||||
(change)="minSpecialChanged()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label class="d-block">{{ "options" | i18n }}</label>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="uppercase"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.uppercase"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useUppercase"
|
||||
attr.aria-label="{{ 'uppercase' | i18n }}"
|
||||
/>
|
||||
<label for="uppercase" class="form-check-label">A-Z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="lowercase"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.lowercase"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useLowercase"
|
||||
attr.aria-label="{{ 'lowercase' | i18n }}"
|
||||
/>
|
||||
<label for="lowercase" class="form-check-label">a-z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="numbers"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[ngModel]="passwordOptions.number"
|
||||
(ngModelChange)="setPasswordOptionsNumber($event)"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useNumbers"
|
||||
attr.aria-label="{{ 'numbers' | i18n }}"
|
||||
/>
|
||||
<label for="numbers" class="form-check-label">0-9</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="special"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[ngModel]="passwordOptions.special"
|
||||
(ngModelChange)="setPasswordOptionsSpecial($event)"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
|
||||
attr.aria-label="{{ 'specialCharacters' | i18n }}"
|
||||
/>
|
||||
<label for="special" class="form-check-label">!@#$%^&*</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="ambiguous"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="avoidAmbiguous"
|
||||
/>
|
||||
<label for="ambiguous" class="form-check-label">{{ "ambiguous" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="d-flex">
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="regenerate()">
|
||||
{{ "regeneratePassword" | i18n }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
|
||||
{{ "copyPassword" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
(click)="history()"
|
||||
appA11yTitle="{{ 'passwordHistory' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-clock bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="type === 'username'">
|
||||
<div aria-labelledby="usernameTypeHeading" class="form-group" role="radiogroup">
|
||||
<div class="d-block">
|
||||
<label id="usernameTypeHeading">{{ "usernameType" | i18n }}</label>
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://bitwarden.com/help/generator/#username-types"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-check" *ngFor="let o of usernameTypeOptions">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
[(ngModel)]="usernameOptions.type"
|
||||
name="UsernameType"
|
||||
id="usernameType_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveUsernameOptions()"
|
||||
[checked]="usernameOptions.type === o.value"
|
||||
/>
|
||||
<label class="form-check-label" for="usernameType_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
<div class="small text-muted">{{ o.desc }}</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="usernameOptions.type === 'forwarded'">
|
||||
<div class="form-group" role="listbox">
|
||||
<label class="d-block">{{ "service" | i18n }}</label>
|
||||
<select
|
||||
id="ForwardTypeDropdown"
|
||||
name="ForwardType"
|
||||
[(ngModel)]="usernameOptions.forwardedService"
|
||||
(change)="saveUsernameOptions()"
|
||||
class="form-control w-auto"
|
||||
>
|
||||
<option *ngFor="let o of forwardOptions" [ngValue]="o.value" role="option">
|
||||
<label class="form-check-label" for="passwordType_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'simplelogin'">
|
||||
<div class="form-group col-4">
|
||||
<label for="simplelogin-apikey">{{ "apiKey" | i18n }}</label>
|
||||
<input
|
||||
id="simplelogin-apikey"
|
||||
class="form-control"
|
||||
type="password"
|
||||
[(ngModel)]="usernameOptions.forwardedSimpleLoginApiKey"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4" *ngIf="isSelfHosted">
|
||||
<label for="simplelogin-baseUrl">{{ "baseUrl" | i18n }}</label>
|
||||
<input
|
||||
id="simplelogin-baseUrl"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="SimpleLoginDomain"
|
||||
[(ngModel)]="usernameOptions.forwardedSimpleLoginBaseUrl"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'duckduckgo'">
|
||||
<div class="form-group col-4">
|
||||
<label for="duckduckgo-apikey">{{ "apiKey" | i18n }}</label>
|
||||
<input
|
||||
id="duckduckgo-apikey"
|
||||
class="form-control"
|
||||
type="password"
|
||||
name="DuckDuckGoApiKey"
|
||||
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
<ng-container *ngIf="passwordOptions.type === 'passphrase'">
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<label for="num-words">{{ "numWords" | i18n }}</label>
|
||||
<input
|
||||
id="num-words"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="3"
|
||||
max="20"
|
||||
[(ngModel)]="passwordOptions.numWords"
|
||||
(blur)="savePasswordOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
|
||||
<input
|
||||
id="word-separator"
|
||||
class="form-control"
|
||||
type="text"
|
||||
maxlength="1"
|
||||
[(ngModel)]="passwordOptions.wordSeparator"
|
||||
(blur)="savePasswordOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'anonaddy'">
|
||||
<div class="form-group col-4">
|
||||
<label for="anonaddy-apikey">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="anonaddy-apikey"
|
||||
class="form-control"
|
||||
type="password"
|
||||
[(ngModel)]="usernameOptions.forwardedAnonAddyApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="anonaddy-domain">{{ "aliasDomain" | i18n }}</label>
|
||||
<input
|
||||
id="anonaddy-domain"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="usernameOptions.forwardedAnonAddyDomain"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4" *ngIf="isSelfHosted">
|
||||
<label for="anonaddy-baseUrl">{{ "baseUrl" | i18n }}</label>
|
||||
<input
|
||||
id="anonaddy-baseUrl"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="AnonAddyDomain"
|
||||
[(ngModel)]="usernameOptions.forwardedAnonAddyBaseUrl"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'firefoxrelay'">
|
||||
<div class="form-group col-4">
|
||||
<label for="firefox-apikey">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="firefox-apikey"
|
||||
class="form-control"
|
||||
type="password"
|
||||
[(ngModel)]="usernameOptions.forwardedFirefoxApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'fastmail'">
|
||||
<div class="form-group col-4">
|
||||
<label for="fastmail-apiToken">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="fastmail-apiToken"
|
||||
class="form-control"
|
||||
type="password"
|
||||
[(ngModel)]="usernameOptions.forwardedFastmailApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'forwardemail'">
|
||||
<div class="form-group col-4">
|
||||
<label for="forwardemail-apikey">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="forwardemail-apikey"
|
||||
class="form-control"
|
||||
type="password"
|
||||
[(ngModel)]="usernameOptions.forwardedForwardEmailApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="forwardemail-domain">{{ "aliasDomain" | i18n }}</label>
|
||||
<input
|
||||
id="forwardemail-domain"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="usernameOptions.forwardedForwardEmailDomain"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="row" *ngIf="usernameOptions.type === 'subaddress'">
|
||||
<div class="form-group col-4">
|
||||
<label for="subaddress-email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="subaddress-email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="usernameOptions.subaddressEmail"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.type === 'catchall'">
|
||||
<div class="form-group col-4">
|
||||
<label for="catchall-domain">{{ "domainName" | i18n }}</label>
|
||||
<input
|
||||
id="catchall-domain"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="usernameOptions.catchallDomain"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="usernameOptions.type === 'word'">
|
||||
<label class="d-block">{{ "options" | i18n }}</label>
|
||||
<div class="row">
|
||||
<label class="d-block">{{ "options" | i18n }}</label>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="capitalizeUsername"
|
||||
id="capitalize"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="saveUsernameOptions()"
|
||||
[(ngModel)]="usernameOptions.wordCapitalize"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.capitalize"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
|
||||
/>
|
||||
<label for="capitalizeUsername" class="form-check-label">{{ "capitalize" | i18n }}</label>
|
||||
<label for="capitalize" class="form-check-label">{{ "capitalize" | i18n }}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="includeNumberUsername"
|
||||
id="include-number"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="saveUsernameOptions()"
|
||||
[(ngModel)]="usernameOptions.wordIncludeNumber"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.includeNumber"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
|
||||
/>
|
||||
<label for="includeNumberUsername" class="form-check-label">{{
|
||||
"includeNumber" | i18n
|
||||
}}</label>
|
||||
<label for="include-number" class="form-check-label">{{ "includeNumber" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="passwordOptions.type === 'password'">
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<label for="length">{{ "length" | i18n }}</label>
|
||||
<input
|
||||
id="length"
|
||||
class="form-control"
|
||||
type="number"
|
||||
[min]="passwordOptions.minLength"
|
||||
max="128"
|
||||
[(ngModel)]="passwordOptions.length"
|
||||
(blur)="savePasswordOptions()"
|
||||
(change)="lengthChanged()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="min-length">{{ "passwordMinLength" | i18n }}</label>
|
||||
<input
|
||||
id="min-length"
|
||||
class="form-control"
|
||||
type="text"
|
||||
readonly="true"
|
||||
[value]="passwordOptions.length"
|
||||
/>
|
||||
<span
|
||||
class="sr-only"
|
||||
attr.aria-label="{{ 'passwordMinLength' | i18n }}"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
{{ passwordOptionsMinLengthForReader$ | async }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="min-number">{{ "minNumbers" | i18n }}</label>
|
||||
<input
|
||||
id="min-number"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
[(ngModel)]="passwordOptions.minNumber"
|
||||
(input)="onPasswordOptionsMinNumberInput($event)"
|
||||
(change)="minNumberChanged()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="min-special">{{ "minSpecial" | i18n }}</label>
|
||||
<input
|
||||
id="min-special"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
[(ngModel)]="passwordOptions.minSpecial"
|
||||
(input)="onPasswordOptionsMinSpecialInput($event)"
|
||||
(change)="minSpecialChanged()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label class="d-block">{{ "options" | i18n }}</label>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="uppercase"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.uppercase"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useUppercase"
|
||||
attr.aria-label="{{ 'uppercase' | i18n }}"
|
||||
/>
|
||||
<label for="uppercase" class="form-check-label">A-Z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="lowercase"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.lowercase"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useLowercase"
|
||||
attr.aria-label="{{ 'lowercase' | i18n }}"
|
||||
/>
|
||||
<label for="lowercase" class="form-check-label">a-z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="numbers"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[ngModel]="passwordOptions.number"
|
||||
(ngModelChange)="setPasswordOptionsNumber($event)"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useNumbers"
|
||||
attr.aria-label="{{ 'numbers' | i18n }}"
|
||||
/>
|
||||
<label for="numbers" class="form-check-label">0-9</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="special"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[ngModel]="passwordOptions.special"
|
||||
(ngModelChange)="setPasswordOptionsSpecial($event)"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
|
||||
attr.aria-label="{{ 'specialCharacters' | i18n }}"
|
||||
/>
|
||||
<label for="special" class="form-check-label">!@#$%^&*</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="ambiguous"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="avoidAmbiguous"
|
||||
/>
|
||||
<label for="ambiguous" class="form-check-label">{{ "ambiguous" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="d-flex">
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="regenerate()">
|
||||
{{ "regeneratePassword" | i18n }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
|
||||
{{ "copyPassword" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
(click)="history()"
|
||||
appA11yTitle="{{ 'passwordHistory' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-clock bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div #form [appApiAction]="usernameGeneratingPromise">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-submit btn-primary"
|
||||
(click)="$any(form).loading ? false : regenerate()"
|
||||
[attr.aria-disabled]="$any(form).loading ? 'true' : null"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "regenerateUsername" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
|
||||
{{ "copyUsername" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #historyTemplate></ng-template>
|
||||
<ng-container *ngIf="type === 'username'">
|
||||
<div aria-labelledby="usernameTypeHeading" class="form-group" role="radiogroup">
|
||||
<div class="d-block">
|
||||
<label id="usernameTypeHeading">{{ "usernameType" | i18n }}</label>
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://bitwarden.com/help/generator/#username-types"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-check" *ngFor="let o of usernameTypeOptions">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
[(ngModel)]="usernameOptions.type"
|
||||
name="UsernameType"
|
||||
id="usernameType_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveUsernameOptions()"
|
||||
[checked]="usernameOptions.type === o.value"
|
||||
/>
|
||||
<label class="form-check-label" for="usernameType_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
<div class="small text-muted">{{ o.desc }}</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="usernameOptions.type === 'forwarded'">
|
||||
<div class="form-group" role="listbox">
|
||||
<label class="d-block">{{ "service" | i18n }}</label>
|
||||
<select
|
||||
id="ForwardTypeDropdown"
|
||||
name="ForwardType"
|
||||
[(ngModel)]="usernameOptions.forwardedService"
|
||||
(change)="saveUsernameOptions()"
|
||||
class="form-control w-auto"
|
||||
>
|
||||
<option *ngFor="let o of forwardOptions" [ngValue]="o.value" role="option">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'simplelogin'">
|
||||
<div class="form-group col-4">
|
||||
<label for="simplelogin-apikey">{{ "apiKey" | i18n }}</label>
|
||||
<input
|
||||
id="simplelogin-apikey"
|
||||
class="form-control"
|
||||
type="password"
|
||||
[(ngModel)]="usernameOptions.forwardedSimpleLoginApiKey"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4" *ngIf="isSelfHosted">
|
||||
<label for="simplelogin-baseUrl">{{ "baseUrl" | i18n }}</label>
|
||||
<input
|
||||
id="simplelogin-baseUrl"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="SimpleLoginDomain"
|
||||
[(ngModel)]="usernameOptions.forwardedSimpleLoginBaseUrl"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'duckduckgo'">
|
||||
<div class="form-group col-4">
|
||||
<label for="duckduckgo-apikey">{{ "apiKey" | i18n }}</label>
|
||||
<input
|
||||
id="duckduckgo-apikey"
|
||||
class="form-control"
|
||||
type="password"
|
||||
name="DuckDuckGoApiKey"
|
||||
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'anonaddy'">
|
||||
<div class="form-group col-4">
|
||||
<label for="anonaddy-apikey">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="anonaddy-apikey"
|
||||
class="form-control"
|
||||
type="password"
|
||||
[(ngModel)]="usernameOptions.forwardedAnonAddyApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="anonaddy-domain">{{ "aliasDomain" | i18n }}</label>
|
||||
<input
|
||||
id="anonaddy-domain"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="usernameOptions.forwardedAnonAddyDomain"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4" *ngIf="isSelfHosted">
|
||||
<label for="anonaddy-baseUrl">{{ "baseUrl" | i18n }}</label>
|
||||
<input
|
||||
id="anonaddy-baseUrl"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="AnonAddyDomain"
|
||||
[(ngModel)]="usernameOptions.forwardedAnonAddyBaseUrl"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'firefoxrelay'">
|
||||
<div class="form-group col-4">
|
||||
<label for="firefox-apikey">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="firefox-apikey"
|
||||
class="form-control"
|
||||
type="password"
|
||||
[(ngModel)]="usernameOptions.forwardedFirefoxApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'fastmail'">
|
||||
<div class="form-group col-4">
|
||||
<label for="fastmail-apiToken">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="fastmail-apiToken"
|
||||
class="form-control"
|
||||
type="password"
|
||||
[(ngModel)]="usernameOptions.forwardedFastmailApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.forwardedService === 'forwardemail'">
|
||||
<div class="form-group col-4">
|
||||
<label for="forwardemail-apikey">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="forwardemail-apikey"
|
||||
class="form-control"
|
||||
type="password"
|
||||
[(ngModel)]="usernameOptions.forwardedForwardEmailApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="forwardemail-domain">{{ "aliasDomain" | i18n }}</label>
|
||||
<input
|
||||
id="forwardemail-domain"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="usernameOptions.forwardedForwardEmailDomain"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="row" *ngIf="usernameOptions.type === 'subaddress'">
|
||||
<div class="form-group col-4">
|
||||
<label for="subaddress-email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="subaddress-email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="usernameOptions.subaddressEmail"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="usernameOptions.type === 'catchall'">
|
||||
<div class="form-group col-4">
|
||||
<label for="catchall-domain">{{ "domainName" | i18n }}</label>
|
||||
<input
|
||||
id="catchall-domain"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="usernameOptions.catchallDomain"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="usernameOptions.type === 'word'">
|
||||
<label class="d-block">{{ "options" | i18n }}</label>
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="capitalizeUsername"
|
||||
type="checkbox"
|
||||
(change)="saveUsernameOptions()"
|
||||
[(ngModel)]="usernameOptions.wordCapitalize"
|
||||
/>
|
||||
<label for="capitalizeUsername" class="form-check-label">{{
|
||||
"capitalize" | i18n
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="includeNumberUsername"
|
||||
type="checkbox"
|
||||
(change)="saveUsernameOptions()"
|
||||
[(ngModel)]="usernameOptions.wordIncludeNumber"
|
||||
/>
|
||||
<label for="includeNumberUsername" class="form-check-label">{{
|
||||
"includeNumber" | i18n
|
||||
}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div #form [appApiAction]="usernameGeneratingPromise">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-submit btn-primary"
|
||||
(click)="$any(form).loading ? false : regenerate()"
|
||||
[attr.aria-disabled]="$any(form).loading ? 'true' : null"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "regenerateUsername" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
|
||||
{{ "copyUsername" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #historyTemplate></ng-template>
|
||||
</bit-container>
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { ImportCollectionServiceAbstraction } from "@bitwarden/importer/core";
|
||||
import { ImportComponent } from "@bitwarden/importer/ui";
|
||||
|
||||
import { SharedModule } from "../../shared";
|
||||
import { CollectionAdminService } from "../../vault/core/collection-admin.service";
|
||||
|
||||
import { ImportCollectionAdminService } from "./import-collection-admin.service";
|
||||
import { ImportWebComponent } from "./import-web.component";
|
||||
|
||||
@Component({
|
||||
templateUrl: "import-web.component.html",
|
||||
standalone: true,
|
||||
imports: [SharedModule, ImportComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: ImportCollectionServiceAbstraction,
|
||||
useClass: ImportCollectionAdminService,
|
||||
deps: [CollectionAdminService],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AdminImportComponent extends ImportWebComponent {}
|
||||
@ -1,18 +1,20 @@
|
||||
<h1 bitTypography="h1">{{ "importData" | i18n }}</h1>
|
||||
<tools-import
|
||||
(formDisabled)="this.disabled = $event"
|
||||
(formLoading)="this.loading = $event"
|
||||
(onSuccessfulImport)="this.onSuccessfulImport($event)"
|
||||
organizationId="{{ routeOrgId }}"
|
||||
></tools-import>
|
||||
<button
|
||||
[disabled]="disabled"
|
||||
[loading]="loading"
|
||||
form="import_form_importForm"
|
||||
bitButton
|
||||
type="submit"
|
||||
bitFormButton
|
||||
buttonType="primary"
|
||||
>
|
||||
{{ "importData" | i18n }}
|
||||
</button>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<tools-import
|
||||
(formDisabled)="this.disabled = $event"
|
||||
(formLoading)="this.loading = $event"
|
||||
(onSuccessfulImport)="this.onSuccessfulImport($event)"
|
||||
></tools-import>
|
||||
<button
|
||||
[disabled]="disabled"
|
||||
[loading]="loading"
|
||||
form="import_form_importForm"
|
||||
bitButton
|
||||
type="submit"
|
||||
bitFormButton
|
||||
buttonType="primary"
|
||||
>
|
||||
{{ "importData" | i18n }}
|
||||
</button>
|
||||
</bit-container>
|
||||
|
||||
@ -1,51 +1,26 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import {
|
||||
OrganizationService,
|
||||
canAccessVaultTab,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ImportComponent } from "@bitwarden/importer/ui";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
@Component({
|
||||
templateUrl: "import-web.component.html",
|
||||
standalone: true,
|
||||
imports: [SharedModule, ImportComponent],
|
||||
imports: [SharedModule, ImportComponent, HeaderModule],
|
||||
})
|
||||
export class ImportWebComponent implements OnInit {
|
||||
protected routeOrgId: string = null;
|
||||
export class ImportWebComponent {
|
||||
protected loading = false;
|
||||
protected disabled = false;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private organizationService: OrganizationService,
|
||||
private router: Router,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.routeOrgId = this.route.snapshot.paramMap.get("organizationId");
|
||||
}
|
||||
constructor(private router: Router) {}
|
||||
|
||||
/**
|
||||
* Callback that is called after a successful import.
|
||||
*/
|
||||
protected async onSuccessfulImport(organizationId: string): Promise<void> {
|
||||
if (!this.routeOrgId) {
|
||||
await this.router.navigate(["vault"]);
|
||||
return;
|
||||
}
|
||||
|
||||
const organization = await firstValueFrom(this.organizationService.get$(organizationId));
|
||||
if (organization == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (canAccessVaultTab(organization)) {
|
||||
await this.router.navigate(["organizations", organizationId, "vault"]);
|
||||
}
|
||||
await this.router.navigate(["vault"]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,63 +1,64 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "dataBreachReport" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{ "breachDesc" | i18n }}</p>
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<label for="username">{{ "username" | i18n }}</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
name="Username"
|
||||
class="form-control"
|
||||
[(ngModel)]="username"
|
||||
required
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "breachCheckUsernameEmail" | i18n }}</small>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p>{{ "breachDesc" | i18n }}</p>
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<label for="username">{{ "username" | i18n }}</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
name="Username"
|
||||
class="form-control"
|
||||
[(ngModel)]="username"
|
||||
required
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "breachCheckUsernameEmail" | i18n }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
|
||||
{{ "checkBreaches" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<div class="mt-4" *ngIf="!form.loading && checkedUsername">
|
||||
<p *ngIf="error">{{ "reportError" | i18n }}...</p>
|
||||
<ng-container *ngIf="!error">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!breachedAccounts.length">
|
||||
{{ "breachUsernameNotFound" | i18n: checkedUsername }}
|
||||
</app-callout>
|
||||
<app-callout type="danger" title="{{ 'breachFound' | i18n }}" *ngIf="breachedAccounts.length">
|
||||
{{ "breachUsernameFound" | i18n: checkedUsername : breachedAccounts.length }}
|
||||
</app-callout>
|
||||
<ul class="list-group list-group-breach" *ngIf="breachedAccounts.length">
|
||||
<li *ngFor="let a of breachedAccounts" class="list-group-item min-height-fix">
|
||||
<div class="row">
|
||||
<div class="col-2 text-center">
|
||||
<img [src]="a.logoPath" alt="" class="img-fluid" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<h3 class="text-lg">{{ a.title }}</h3>
|
||||
<p [innerHTML]="a.description"></p>
|
||||
<p class="mb-1">{{ "compromisedData" | i18n }}:</p>
|
||||
<ul>
|
||||
<li *ngFor="let d of a.dataClasses">{{ d }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<dl>
|
||||
<dt>{{ "website" | i18n }}</dt>
|
||||
<dd>{{ a.domain }}</dd>
|
||||
<dt>{{ "affectedUsers" | i18n }}</dt>
|
||||
<dd>{{ a.pwnCount | number }}</dd>
|
||||
<dt>{{ "breachOccurred" | i18n }}</dt>
|
||||
<dd>{{ a.breachDate | date: "mediumDate" }}</dd>
|
||||
<dt>{{ "breachReported" | i18n }}</dt>
|
||||
<dd>{{ a.addedDate | date: "mediumDate" }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
</div>
|
||||
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
|
||||
{{ "checkBreaches" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<div class="mt-4" *ngIf="!form.loading && checkedUsername">
|
||||
<p *ngIf="error">{{ "reportError" | i18n }}...</p>
|
||||
<ng-container *ngIf="!error">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!breachedAccounts.length">
|
||||
{{ "breachUsernameNotFound" | i18n: checkedUsername }}
|
||||
</app-callout>
|
||||
<app-callout type="danger" title="{{ 'breachFound' | i18n }}" *ngIf="breachedAccounts.length">
|
||||
{{ "breachUsernameFound" | i18n: checkedUsername : breachedAccounts.length }}
|
||||
</app-callout>
|
||||
<ul class="list-group list-group-breach" *ngIf="breachedAccounts.length">
|
||||
<li *ngFor="let a of breachedAccounts" class="list-group-item min-height-fix">
|
||||
<div class="row">
|
||||
<div class="col-2 text-center">
|
||||
<img [src]="a.logoPath" alt="" class="img-fluid" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<h3 class="text-lg">{{ a.title }}</h3>
|
||||
<p [innerHTML]="a.description"></p>
|
||||
<p class="mb-1">{{ "compromisedData" | i18n }}:</p>
|
||||
<ul>
|
||||
<li *ngFor="let d of a.dataClasses">{{ d }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<dl>
|
||||
<dt>{{ "website" | i18n }}</dt>
|
||||
<dd>{{ a.domain }}</dd>
|
||||
<dt>{{ "affectedUsers" | i18n }}</dt>
|
||||
<dd>{{ a.pwnCount | number }}</dd>
|
||||
<dt>{{ "breachOccurred" | i18n }}</dt>
|
||||
<dd>{{ a.breachDate | date: "mediumDate" }}</dd>
|
||||
<dt>{{ "breachReported" | i18n }}</dt>
|
||||
<dd>{{ a.addedDate | date: "mediumDate" }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
</div>
|
||||
</bit-container>
|
||||
|
||||
@ -1,72 +1,77 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "exposedPasswordsReport" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{ "exposedPasswordsReportDesc" | i18n }}</p>
|
||||
<button type="submit" buttonType="primary" bitButton [loading]="loading" (click)="load()">
|
||||
{{ "checkExposedPasswords" | i18n }}
|
||||
</button>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noExposedPasswords" | i18n }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'exposedPasswordsFound' | i18n }}" [useAlertRole]="true">
|
||||
{{ "exposedPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p>{{ "exposedPasswordsReportDesc" | i18n }}</p>
|
||||
<button type="submit" buttonType="primary" bitButton [loading]="loading" (click)="load()">
|
||||
{{ "checkExposedPasswords" | i18n }}
|
||||
</button>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noExposedPasswords" | i18n }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{ c.name }}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'exposedPasswordsFound' | i18n }}" [useAlertRole]="true">
|
||||
{{ "exposedPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="selectCipher(c)"
|
||||
title="{{ 'editItem' | i18n }}"
|
||||
>{{ c.name }}</a
|
||||
>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{ c.name }}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<app-org-badge
|
||||
*ngIf="!organization"
|
||||
[disabled]="disabled"
|
||||
[organizationId]="c.organizationId"
|
||||
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<app-org-badge
|
||||
*ngIf="!organization"
|
||||
[disabled]="disabled"
|
||||
[organizationId]="c.organizationId"
|
||||
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
|
||||
appStopProp
|
||||
>
|
||||
</app-org-badge>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span bitBadge variant="warning">
|
||||
{{ "exposedXTimes" | i18n: (exposedPasswordMap.get(c.id) | number) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
>
|
||||
</app-org-badge>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span bitBadge variant="warning">
|
||||
{{ "exposedXTimes" | i18n: (exposedPasswordMap.get(c.id) | number) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
</bit-container>
|
||||
|
||||
@ -1,88 +1,79 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{ "inactive2faReport" | i18n }}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<p>{{ "inactive2faReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noInactive2fa" | i18n }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'inactive2faFound' | i18n }}">
|
||||
{{ "inactive2faFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p>{{ "inactive2faReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noInactive2fa" | i18n }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'inactive2faFound' | i18n }}">
|
||||
{{ "inactive2faFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<app-org-badge
|
||||
*ngIf="!organization"
|
||||
[disabled]="disabled"
|
||||
[organizationId]="c.organizationId"
|
||||
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<app-org-badge
|
||||
*ngIf="!organization"
|
||||
[disabled]="disabled"
|
||||
[organizationId]="c.organizationId"
|
||||
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
|
||||
appStopProp
|
||||
>
|
||||
</app-org-badge>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a
|
||||
bitBadge
|
||||
href="{{ cipherDocs.get(c.id) }}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
*ngIf="cipherDocs.has(c.id)"
|
||||
>
|
||||
{{ "instructions" | i18n }}</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
>
|
||||
</app-org-badge>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a
|
||||
bitBadge
|
||||
href="{{ cipherDocs.get(c.id) }}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
*ngIf="cipherDocs.has(c.id)"
|
||||
>
|
||||
{{ "instructions" | i18n }}</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
</bit-container>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "reports" | i18n }}</h1>
|
||||
</div>
|
||||
<app-header></app-header>
|
||||
|
||||
<p>{{ "reportsDesc" | i18n }}</p>
|
||||
<bit-container>
|
||||
<p>{{ "reportsDesc" | i18n }}</p>
|
||||
|
||||
<app-report-list [reports]="reports"></app-report-list>
|
||||
<app-report-list [reports]="reports"></app-report-list>
|
||||
</bit-container>
|
||||
|
||||
@ -1,87 +1,82 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{ "reusedPasswordsReport" | i18n }}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<p>{{ "reusedPasswordsReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noReusedPasswords" | i18n }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'reusedPasswordsFound' | i18n }}">
|
||||
{{ "reusedPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p>{{ "reusedPasswordsReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noReusedPasswords" | i18n }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{ c.name }}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'reusedPasswordsFound' | i18n }}">
|
||||
{{ "reusedPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="selectCipher(c)"
|
||||
title="{{ 'editItem' | i18n }}"
|
||||
>{{ c.name }}</a
|
||||
>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{ c.name }}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<app-org-badge
|
||||
*ngIf="!organization"
|
||||
[disabled]="disabled"
|
||||
[organizationId]="c.organizationId"
|
||||
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<app-org-badge
|
||||
*ngIf="!organization"
|
||||
[disabled]="disabled"
|
||||
[organizationId]="c.organizationId"
|
||||
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
|
||||
appStopProp
|
||||
>
|
||||
</app-org-badge>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span bitBadge variant="warning">
|
||||
{{ "reusedXTimes" | i18n: passwordUseMap.get(c.login.password) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
>
|
||||
</app-org-badge>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span bitBadge variant="warning">
|
||||
{{ "reusedXTimes" | i18n: passwordUseMap.get(c.login.password) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
</bit-container>
|
||||
|
||||
@ -1,77 +1,68 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{ "unsecuredWebsitesReport" | i18n }}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<p>{{ "unsecuredWebsitesReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noUnsecuredWebsites" | i18n }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'unsecuredWebsitesFound' | i18n }}">
|
||||
{{ "unsecuredWebsitesFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p>{{ "unsecuredWebsitesReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noUnsecuredWebsites" | i18n }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'unsecuredWebsitesFound' | i18n }}">
|
||||
{{ "unsecuredWebsitesFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<app-org-badge
|
||||
*ngIf="!organization"
|
||||
[disabled]="disabled"
|
||||
[organizationId]="c.organizationId"
|
||||
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<app-org-badge
|
||||
*ngIf="!organization"
|
||||
[disabled]="disabled"
|
||||
[organizationId]="c.organizationId"
|
||||
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
|
||||
appStopProp
|
||||
>
|
||||
</app-org-badge>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
>
|
||||
</app-org-badge>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
</bit-container>
|
||||
|
||||
@ -1,87 +1,82 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{ "weakPasswordsReport" | i18n }}
|
||||
<small *ngIf="hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<p>{{ "weakPasswordsReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noWeakPasswords" | i18n }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'weakPasswordsFound' | i18n }}">
|
||||
{{ "weakPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p>{{ "weakPasswordsReportDesc" | i18n }}</p>
|
||||
<div *ngIf="!hasLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="hasLoaded">
|
||||
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
|
||||
{{ "noWeakPasswords" | i18n }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{ c.name }}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<app-callout type="danger" title="{{ 'weakPasswordsFound' | i18n }}">
|
||||
{{ "weakPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="selectCipher(c)"
|
||||
title="{{ 'editItem' | i18n }}"
|
||||
>{{ c.name }}</a
|
||||
>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{ c.name }}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<app-org-badge
|
||||
*ngIf="!organization"
|
||||
[disabled]="disabled"
|
||||
[organizationId]="c.organizationId"
|
||||
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<app-org-badge
|
||||
*ngIf="!organization"
|
||||
[disabled]="disabled"
|
||||
[organizationId]="c.organizationId"
|
||||
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
|
||||
appStopProp
|
||||
>
|
||||
</app-org-badge>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span bitBadge [variant]="passwordStrengthMap.get(c.id)[1]">
|
||||
{{ passwordStrengthMap.get(c.id)[0] | i18n }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
>
|
||||
</app-org-badge>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span bitBadge [variant]="passwordStrengthMap.get(c.id)[1]">
|
||||
{{ passwordStrengthMap.get(c.id)[0] | i18n }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
</bit-container>
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
<div class="container page-content">
|
||||
<router-outlet></router-outlet>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col">
|
||||
<a bitButton routerLink="./" *ngIf="!homepage">
|
||||
<i class="bwi bwi-angle-left" aria-hidden="true"></i>
|
||||
{{ "backToReports" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
<div class="col">
|
||||
<a bitButton routerLink="./" *ngIf="!homepage">
|
||||
<i class="bwi bwi-angle-left" aria-hidden="true"></i>
|
||||
{{ "backToReports" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -20,7 +20,12 @@ const routes: Routes = [
|
||||
component: ReportsLayoutComponent,
|
||||
canActivate: [AuthGuard],
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", component: ReportsHomeComponent, data: { homepage: true } },
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
component: ReportsHomeComponent,
|
||||
data: { titleId: "reports", homepage: true },
|
||||
},
|
||||
{
|
||||
path: "breach-report",
|
||||
component: BreachReportComponent,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
@ -24,6 +25,7 @@ import { ReportsSharedModule } from "./shared";
|
||||
ReportsRoutingModule,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
HeaderModule,
|
||||
],
|
||||
declarations: [
|
||||
BreachReportComponent,
|
||||
|
||||
@ -1,221 +1,206 @@
|
||||
<div class="container page-content">
|
||||
<bit-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
|
||||
{{ "sendDisabledWarning" | i18n }}
|
||||
</bit-callout>
|
||||
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
||||
<div class="groupings tw-col-span-3">
|
||||
<div class="card vault-filters">
|
||||
<div class="card-header d-flex">
|
||||
{{ "filters" | i18n }}
|
||||
<app-header>
|
||||
<ng-container slot="title-suffix">
|
||||
<small #actionSpinner [appApiAction]="actionPromise">
|
||||
<ng-container *ngIf="$any(actionSpinner).loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
</small>
|
||||
</ng-container>
|
||||
|
||||
<button type="button" bitButton buttonType="primary" (click)="addSend()" [disabled]="disableSend">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "createSend" | i18n }}
|
||||
</button>
|
||||
</app-header>
|
||||
|
||||
<bit-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
|
||||
{{ "sendDisabledWarning" | i18n }}
|
||||
</bit-callout>
|
||||
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
||||
<div class="groupings tw-col-span-3">
|
||||
<div class="card vault-filters">
|
||||
<div class="card-header d-flex">
|
||||
{{ "filters" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tw-mb-4">
|
||||
<bit-search
|
||||
[(ngModel)]="searchText"
|
||||
[placeholder]="'searchSends' | i18n"
|
||||
(input)="searchTextChanged()"
|
||||
appAutofocus
|
||||
/>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tw-mb-4">
|
||||
<bit-search
|
||||
[(ngModel)]="searchText"
|
||||
[placeholder]="'searchSends' | i18n"
|
||||
(input)="searchTextChanged()"
|
||||
appAutofocus
|
||||
/>
|
||||
</div>
|
||||
<div class="filter">
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option" [ngClass]="{ active: selectedAll }">
|
||||
<span class="filter-buttons">
|
||||
<button type="button" class="filter-button" appStopClick (click)="selectAll()">
|
||||
<i class="bwi bwi-fw bwi-filter"></i>{{ "allSends" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="filter">
|
||||
<div class="filter-heading">
|
||||
<h3>{{ "types" | i18n }}</h3>
|
||||
</div>
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.Text }">
|
||||
<span class="filter-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="filter-button"
|
||||
appStopClick
|
||||
(click)="selectType(sendType.Text)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-file-text"></i>{{ "sendTypeText" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.File }">
|
||||
<span class="filter-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="filter-button"
|
||||
appStopClick
|
||||
(click)="selectType(sendType.File)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-file"></i>{{ "sendTypeFile" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="filter">
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option" [ngClass]="{ active: selectedAll }">
|
||||
<span class="filter-buttons">
|
||||
<button type="button" class="filter-button" appStopClick (click)="selectAll()">
|
||||
<i class="bwi bwi-fw bwi-filter"></i>{{ "allSends" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="filter">
|
||||
<div class="filter-heading">
|
||||
<h3>{{ "types" | i18n }}</h3>
|
||||
</div>
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.Text }">
|
||||
<span class="filter-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="filter-button"
|
||||
appStopClick
|
||||
(click)="selectType(sendType.Text)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-file-text"></i>{{ "sendTypeText" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.File }">
|
||||
<span class="filter-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="filter-button"
|
||||
appStopClick
|
||||
(click)="selectType(sendType.File)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-file"></i>{{ "sendTypeFile" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-col-span-9">
|
||||
<div class="tw-flex">
|
||||
<h1 bitTypography="h1">
|
||||
{{ "send" | i18n }}
|
||||
<small #actionSpinner [appApiAction]="actionPromise">
|
||||
<ng-container *ngIf="$any(actionSpinner).loading">
|
||||
</div>
|
||||
<div class="tw-col-span-9">
|
||||
<!--Listing Table-->
|
||||
<bit-table [dataSource]="dataSource" *ngIf="filteredSends && filteredSends.length">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell bitSortable="name" default>{{ "name" | i18n }}</th>
|
||||
<th bitCell bitSortable="deletionDate">{{ "deletionDate" | i18n }}</th>
|
||||
<th bitCell>{{ "options" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body let-rows$>
|
||||
<tr bitRow *ngFor="let s of rows$ | async">
|
||||
<td bitCell (click)="editSend(s)" class="tw-cursor-pointer">
|
||||
<span class="tw-mr-2" aria-hidden="true">
|
||||
<i class="bwi bwi-fw bwi-lg bwi-file" *ngIf="s.type == sendType.File"></i>
|
||||
<i class="bwi bwi-fw bwi-lg bwi-file-text" *ngIf="s.type == sendType.Text"></i>
|
||||
</span>
|
||||
<button type="button" bitLink>
|
||||
{{ s.name }}
|
||||
</button>
|
||||
<ng-container *ngIf="s.disabled">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
class="bwi bwi-exclamation-triangle"
|
||||
appStopProp
|
||||
title="{{ 'disabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
<span class="sr-only">{{ "disabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
</small>
|
||||
</h1>
|
||||
<div class="tw-ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="addSend()"
|
||||
[disabled]="disableSend"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "createSend" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!--Listing Table-->
|
||||
<bit-table [dataSource]="dataSource" *ngIf="filteredSends && filteredSends.length">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell bitSortable="name" default>{{ "name" | i18n }}</th>
|
||||
<th bitCell bitSortable="deletionDate">{{ "deletionDate" | i18n }}</th>
|
||||
<th bitCell>{{ "options" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body let-rows$>
|
||||
<tr bitRow *ngFor="let s of rows$ | async">
|
||||
<td bitCell (click)="editSend(s)" class="tw-cursor-pointer">
|
||||
<span class="tw-mr-2" aria-hidden="true">
|
||||
<i class="bwi bwi-fw bwi-lg bwi-file" *ngIf="s.type == sendType.File"></i>
|
||||
<i class="bwi bwi-fw bwi-lg bwi-file-text" *ngIf="s.type == sendType.Text"></i>
|
||||
</span>
|
||||
<button type="button" bitLink>
|
||||
{{ s.name }}
|
||||
<ng-container *ngIf="s.password">
|
||||
<i
|
||||
class="bwi bwi-key"
|
||||
appStopProp
|
||||
title="{{ 'password' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "password" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.maxAccessCountReached">
|
||||
<i
|
||||
class="bwi bwi-ban"
|
||||
appStopProp
|
||||
title="{{ 'maxAccessCountReached' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "maxAccessCountReached" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.expired">
|
||||
<i
|
||||
class="bwi bwi-clock"
|
||||
appStopProp
|
||||
title="{{ 'expired' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "expired" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.pendingDelete">
|
||||
<i
|
||||
class="bwi bwi-trash"
|
||||
appStopProp
|
||||
title="{{ 'pendingDeletion' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td bitCell class="tw-text-muted" (click)="editSend(s)" class="tw-cursor-pointer">
|
||||
<small bitTypography="body2" appStopProp>{{ s.deletionDate | date: "medium" }}</small>
|
||||
</td>
|
||||
<td bitCell class="tw-w-0 tw-text-right">
|
||||
<button
|
||||
type="button"
|
||||
[bitMenuTriggerFor]="sendOptions"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
<bit-menu #sendOptions>
|
||||
<button type="button" bitMenuItem (click)="copy(s)">
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copySendLink" | i18n }}
|
||||
</button>
|
||||
<ng-container *ngIf="s.disabled">
|
||||
<i
|
||||
class="bwi bwi-exclamation-triangle"
|
||||
appStopProp
|
||||
title="{{ 'disabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "disabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.password">
|
||||
<i
|
||||
class="bwi bwi-key"
|
||||
appStopProp
|
||||
title="{{ 'password' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "password" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.maxAccessCountReached">
|
||||
<i
|
||||
class="bwi bwi-ban"
|
||||
appStopProp
|
||||
title="{{ 'maxAccessCountReached' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "maxAccessCountReached" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.expired">
|
||||
<i
|
||||
class="bwi bwi-clock"
|
||||
appStopProp
|
||||
title="{{ 'expired' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "expired" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.pendingDelete">
|
||||
<i
|
||||
class="bwi bwi-trash"
|
||||
appStopProp
|
||||
title="{{ 'pendingDeletion' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td bitCell class="tw-text-muted" (click)="editSend(s)" class="tw-cursor-pointer">
|
||||
<small bitTypography="body2" appStopProp>{{ s.deletionDate | date: "medium" }}</small>
|
||||
</td>
|
||||
<td bitCell class="tw-w-0 tw-text-right">
|
||||
<button
|
||||
type="button"
|
||||
[bitMenuTriggerFor]="sendOptions"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
<bit-menu #sendOptions>
|
||||
<button type="button" bitMenuItem (click)="copy(s)">
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copySendLink" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="removePassword(s)"
|
||||
*ngIf="s.password && !disableSend"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "removePassword" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="delete(s)">
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
<div class="no-items" *ngIf="filteredSends && !filteredSends.length">
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="loaded">
|
||||
<bit-no-items [icon]="noItemIcon" class="tw-text-main">
|
||||
<ng-container slot="title">{{ "sendsNoItemsTitle" | i18n }}</ng-container>
|
||||
<ng-container slot="description">{{ "sendsNoItemsMessage" | i18n }}</ng-container>
|
||||
<button
|
||||
slot="button"
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="addSend()"
|
||||
>
|
||||
<i class="bwi bwi-plus" aria-hidden="true"></i>
|
||||
{{ "createSend" | i18n }}
|
||||
</button>
|
||||
</bit-no-items>
|
||||
</ng-container>
|
||||
</div>
|
||||
bitMenuItem
|
||||
(click)="removePassword(s)"
|
||||
*ngIf="s.password && !disableSend"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "removePassword" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="delete(s)">
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
<div class="no-items" *ngIf="filteredSends && !filteredSends.length">
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="loaded">
|
||||
<bit-no-items [icon]="noItemIcon" class="tw-text-main">
|
||||
<ng-container slot="title">{{ "sendsNoItemsTitle" | i18n }}</ng-container>
|
||||
<ng-container slot="description">{{ "sendsNoItemsMessage" | i18n }}</ng-container>
|
||||
<button slot="button" type="button" bitButton buttonType="secondary" (click)="addSend()">
|
||||
<i class="bwi bwi-plus" aria-hidden="true"></i>
|
||||
{{ "createSend" | i18n }}
|
||||
</button>
|
||||
</bit-no-items>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -14,6 +14,7 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { DialogService, NoItemsModule, SearchModule, TableDataSource } from "@bitwarden/components";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
@ -24,7 +25,7 @@ const BroadcasterSubscriptionId = "SendComponent";
|
||||
@Component({
|
||||
selector: "app-send",
|
||||
standalone: true,
|
||||
imports: [SharedModule, SearchModule, NoItemsModule],
|
||||
imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule],
|
||||
templateUrl: "send.component.html",
|
||||
})
|
||||
export class SendComponent extends BaseSendComponent {
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">{{ "tools" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="generator" class="list-group-item" routerLinkActive="active">
|
||||
{{ "generator" | i18n }}
|
||||
</a>
|
||||
<a routerLink="import" class="list-group-item" routerLinkActive="active">
|
||||
{{ "importData" | i18n }}
|
||||
</a>
|
||||
<a routerLink="export" class="list-group-item" routerLinkActive="active">
|
||||
{{ "exportVault" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,79 +1,101 @@
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
[formGroup]="exportForm"
|
||||
*ngIf="exportForm"
|
||||
>
|
||||
<h1 bitTypography="h1">{{ "exportVault" | i18n }}</h1>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-callout type="danger" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
|
||||
{{ "personalVaultExportPolicyInEffect" | i18n }}
|
||||
</bit-callout>
|
||||
<app-export-scope-callout
|
||||
[organizationId]="organizationId"
|
||||
*ngIf="!disabledByPolicy"
|
||||
></app-export-scope-callout>
|
||||
<bit-container>
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
[formGroup]="exportForm"
|
||||
*ngIf="exportForm"
|
||||
>
|
||||
<bit-callout type="danger" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
|
||||
{{ "personalVaultExportPolicyInEffect" | i18n }}
|
||||
</bit-callout>
|
||||
<app-export-scope-callout
|
||||
[organizationId]="organizationId"
|
||||
*ngIf="!disabledByPolicy"
|
||||
></app-export-scope-callout>
|
||||
|
||||
<ng-container *ngIf="organizations$ | async as organizations">
|
||||
<bit-form-field *ngIf="organizations.length > 0">
|
||||
<bit-label>{{ "exportFrom" | i18n }}</bit-label>
|
||||
<bit-select formControlName="vaultSelector">
|
||||
<bit-option [label]="'myVault' | i18n" value="myVault" icon="bwi-user" />
|
||||
<bit-option
|
||||
*ngFor="let o of organizations$ | async"
|
||||
[value]="o.id"
|
||||
[label]="o.name"
|
||||
icon="bwi-business"
|
||||
/>
|
||||
<ng-container *ngIf="organizations$ | async as organizations">
|
||||
<bit-form-field *ngIf="organizations.length > 0">
|
||||
<bit-label>{{ "exportFrom" | i18n }}</bit-label>
|
||||
<bit-select formControlName="vaultSelector">
|
||||
<bit-option [label]="'myVault' | i18n" value="myVault" icon="bwi-user" />
|
||||
<bit-option
|
||||
*ngFor="let o of organizations$ | async"
|
||||
[value]="o.id"
|
||||
[label]="o.name"
|
||||
icon="bwi-business"
|
||||
/>
|
||||
</bit-select>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "fileFormat" | i18n }}</bit-label>
|
||||
<bit-select formControlName="format">
|
||||
<bit-option *ngFor="let f of formatOptions" [value]="f.value" [label]="f.name" />
|
||||
</bit-select>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "fileFormat" | i18n }}</bit-label>
|
||||
<bit-select formControlName="format">
|
||||
<bit-option *ngFor="let f of formatOptions" [value]="f.value" [label]="f.name" />
|
||||
</bit-select>
|
||||
</bit-form-field>
|
||||
<ng-container *ngIf="format === 'encrypted_json'">
|
||||
<bit-radio-group formControlName="fileEncryptionType" aria-label="exportTypeHeading">
|
||||
<bit-label>{{ "exportTypeHeading" | i18n }}</bit-label>
|
||||
|
||||
<ng-container *ngIf="format === 'encrypted_json'">
|
||||
<bit-radio-group formControlName="fileEncryptionType" aria-label="exportTypeHeading">
|
||||
<bit-label>{{ "exportTypeHeading" | i18n }}</bit-label>
|
||||
<bit-radio-button
|
||||
id="AccountEncrypted"
|
||||
name="fileEncryptionType"
|
||||
class="tw-block"
|
||||
[value]="encryptedExportType.AccountEncrypted"
|
||||
checked="fileEncryptionType === encryptedExportType.AccountEncrypted"
|
||||
>
|
||||
<bit-label>{{ "accountRestricted" | i18n }}</bit-label>
|
||||
<bit-hint>{{ "accountRestrictedOptionDescription" | i18n }}</bit-hint>
|
||||
</bit-radio-button>
|
||||
|
||||
<bit-radio-button
|
||||
id="AccountEncrypted"
|
||||
name="fileEncryptionType"
|
||||
class="tw-block"
|
||||
[value]="encryptedExportType.AccountEncrypted"
|
||||
checked="fileEncryptionType === encryptedExportType.AccountEncrypted"
|
||||
>
|
||||
<bit-label>{{ "accountRestricted" | i18n }}</bit-label>
|
||||
<bit-hint>{{ "accountRestrictedOptionDescription" | i18n }}</bit-hint>
|
||||
</bit-radio-button>
|
||||
<bit-radio-button
|
||||
id="FileEncrypted"
|
||||
name="fileEncryptionType"
|
||||
class="tw-block"
|
||||
[value]="encryptedExportType.FileEncrypted"
|
||||
checked="fileEncryptionType === encryptedExportType.FileEncrypted"
|
||||
>
|
||||
<bit-label>{{ "passwordProtected" | i18n }}</bit-label>
|
||||
<bit-hint>{{ "passwordProtectedOptionDescription" | i18n }}</bit-hint>
|
||||
</bit-radio-button>
|
||||
</bit-radio-group>
|
||||
|
||||
<bit-radio-button
|
||||
id="FileEncrypted"
|
||||
name="fileEncryptionType"
|
||||
class="tw-block"
|
||||
[value]="encryptedExportType.FileEncrypted"
|
||||
checked="fileEncryptionType === encryptedExportType.FileEncrypted"
|
||||
>
|
||||
<bit-label>{{ "passwordProtected" | i18n }}</bit-label>
|
||||
<bit-hint>{{ "passwordProtectedOptionDescription" | i18n }}</bit-hint>
|
||||
</bit-radio-button>
|
||||
</bit-radio-group>
|
||||
|
||||
<ng-container *ngIf="fileEncryptionType == encryptedExportType.FileEncrypted">
|
||||
<div class="tw-mb-3">
|
||||
<ng-container *ngIf="fileEncryptionType == encryptedExportType.FileEncrypted">
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "filePassword" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="password"
|
||||
id="filePassword"
|
||||
formControlName="filePassword"
|
||||
name="password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
bitSuffix
|
||||
bitIconButton
|
||||
bitPasswordInputToggle
|
||||
[(toggled)]="showFilePassword"
|
||||
></button>
|
||||
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<app-password-strength [password]="filePassword" [showText]="true">
|
||||
</app-password-strength>
|
||||
</div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "filePassword" | i18n }}</bit-label>
|
||||
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="password"
|
||||
id="filePassword"
|
||||
formControlName="filePassword"
|
||||
name="password"
|
||||
id="confirmFilePassword"
|
||||
formControlName="confirmFilePassword"
|
||||
name="confirmFilePassword"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@ -82,37 +104,18 @@
|
||||
bitPasswordInputToggle
|
||||
[(toggled)]="showFilePassword"
|
||||
></button>
|
||||
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<app-password-strength [password]="filePassword" [showText]="true"> </app-password-strength>
|
||||
</div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="password"
|
||||
id="confirmFilePassword"
|
||||
formControlName="confirmFilePassword"
|
||||
name="confirmFilePassword"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
bitSuffix
|
||||
bitIconButton
|
||||
bitPasswordInputToggle
|
||||
[(toggled)]="showFilePassword"
|
||||
></button>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<button
|
||||
bitButton
|
||||
type="submit"
|
||||
buttonType="primary"
|
||||
[loading]="form.loading"
|
||||
[disabled]="disabledByPolicy"
|
||||
>
|
||||
{{ "confirmFormat" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<button
|
||||
bitButton
|
||||
type="submit"
|
||||
buttonType="primary"
|
||||
[loading]="form.loading"
|
||||
[disabled]="disabledByPolicy"
|
||||
>
|
||||
{{ "confirmFormat" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
</bit-container>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SearchModule } from "@bitwarden/components";
|
||||
|
||||
import { VaultFilterSharedModule } from "../../individual-vault/vault-filter/shared/vault-filter-shared.module";
|
||||
|
||||
import { LinkSsoDirective } from "./components/link-sso.directive";
|
||||
@ -9,7 +11,7 @@ import { VaultFilterService as VaultFilterServiceAbstraction } from "./services/
|
||||
import { VaultFilterService } from "./services/vault-filter.service";
|
||||
|
||||
@NgModule({
|
||||
imports: [VaultFilterSharedModule],
|
||||
imports: [VaultFilterSharedModule, SearchModule],
|
||||
declarations: [VaultFilterComponent, OrganizationOptionsComponent, LinkSsoDirective],
|
||||
exports: [VaultFilterComponent],
|
||||
providers: [
|
||||
|
||||
@ -1,83 +1,71 @@
|
||||
<div class="tw-mb-4 tw-flex tw-items-start tw-justify-between">
|
||||
<div>
|
||||
<bit-breadcrumbs *ngIf="showBreadcrumbs">
|
||||
<app-header [title]="title" [icon]="icon">
|
||||
<bit-breadcrumbs *ngIf="showBreadcrumbs" slot="breadcrumbs">
|
||||
<bit-breadcrumb
|
||||
*ngIf="activeOrganizationId"
|
||||
[route]="[]"
|
||||
[queryParams]="{ organizationId: activeOrganizationId, collectionId: All }"
|
||||
queryParamsHandling="merge"
|
||||
>
|
||||
{{ activeOrganizationId | orgNameFromId: organizations }} {{ "vault" | i18n | lowercase }}
|
||||
</bit-breadcrumb>
|
||||
<ng-container>
|
||||
<bit-breadcrumb
|
||||
*ngIf="activeOrganizationId"
|
||||
*ngFor="let collection of collections"
|
||||
icon="bwi-collection"
|
||||
[route]="[]"
|
||||
[queryParams]="{ organizationId: activeOrganizationId, collectionId: All }"
|
||||
[queryParams]="{ collectionId: collection.id }"
|
||||
queryParamsHandling="merge"
|
||||
>
|
||||
{{ activeOrganizationId | orgNameFromId: organizations }} {{ "vault" | i18n | lowercase }}
|
||||
{{ collection.name }}
|
||||
</bit-breadcrumb>
|
||||
<ng-container>
|
||||
<bit-breadcrumb
|
||||
*ngFor="let collection of collections"
|
||||
icon="bwi-collection"
|
||||
[route]="[]"
|
||||
[queryParams]="{ collectionId: collection.id }"
|
||||
queryParamsHandling="merge"
|
||||
</ng-container>
|
||||
</bit-breadcrumbs>
|
||||
|
||||
<ng-container slot="title-suffix">
|
||||
<ng-container *ngIf="collection != null && (canEditCollection || canDeleteCollection)">
|
||||
<button
|
||||
bitIconButton="bwi-angle-down"
|
||||
[bitMenuTriggerFor]="editCollectionMenu"
|
||||
size="small"
|
||||
type="button"
|
||||
aria-haspopup="true"
|
||||
></button>
|
||||
<bit-menu #editCollectionMenu>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection"
|
||||
bitMenuItem
|
||||
(click)="editCollection(CollectionDialogTabType.Info)"
|
||||
>
|
||||
{{ collection.name }}
|
||||
</bit-breadcrumb>
|
||||
</ng-container>
|
||||
</bit-breadcrumbs>
|
||||
<h1 class="tw-mb-0 tw-mt-1 tw-flex tw-items-center tw-space-x-2">
|
||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
||||
{{ "editInfo" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection"
|
||||
bitMenuItem
|
||||
(click)="editCollection(CollectionDialogTabType.Access)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "access" | i18n }}
|
||||
</button>
|
||||
<button type="button" *ngIf="canDeleteCollection" bitMenuItem (click)="deleteCollection()">
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
<small *ngIf="loading">
|
||||
<i
|
||||
*ngIf="filter.collectionId && filter.collectionId !== All"
|
||||
class="bwi bwi-collection"
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{{ title }}</span>
|
||||
<ng-container *ngIf="collection != null && (canEditCollection || canDeleteCollection)">
|
||||
<button
|
||||
bitIconButton="bwi-angle-down"
|
||||
[bitMenuTriggerFor]="editCollectionMenu"
|
||||
size="small"
|
||||
type="button"
|
||||
aria-haspopup
|
||||
></button>
|
||||
<bit-menu #editCollectionMenu>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection"
|
||||
bitMenuItem
|
||||
(click)="editCollection(CollectionDialogTabType.Info)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
||||
{{ "editInfo" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection"
|
||||
bitMenuItem
|
||||
(click)="editCollection(CollectionDialogTabType.Access)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "access" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canDeleteCollection"
|
||||
bitMenuItem
|
||||
(click)="deleteCollection()"
|
||||
>
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
<small *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="filter.type !== 'trash'" class="tw-shrink-0">
|
||||
<div appListDropdown>
|
||||
@ -107,4 +95,4 @@
|
||||
</bit-menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</app-header>
|
||||
|
||||
@ -103,6 +103,10 @@ export class VaultHeaderComponent {
|
||||
return this.i18nService.t("allVaults");
|
||||
}
|
||||
|
||||
protected get icon() {
|
||||
return this.filter.collectionId && this.filter.collectionId !== All ? "bwi-collection" : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of collection filters that form a chain from the organization root to currently selected collection.
|
||||
* Begins from the organization root and excludes the currently selected collection.
|
||||
|
||||
@ -1,129 +1,126 @@
|
||||
<div class="container page-content">
|
||||
<app-vault-onboarding [ciphers]="ciphers" (onAddCipher)="addCipher()"> </app-vault-onboarding>
|
||||
<app-vault-header
|
||||
[filter]="filter"
|
||||
[loading]="refreshing && !performingInitialLoad"
|
||||
[organizations]="allOrganizations"
|
||||
[canCreateCollections]="canCreateCollections"
|
||||
[collection]="selectedCollection"
|
||||
(onAddCipher)="addCipher()"
|
||||
(onAddCollection)="addCollection()"
|
||||
(onAddFolder)="addFolder()"
|
||||
(onEditCollection)="editCollection(selectedCollection.node, $event.tab)"
|
||||
(onDeleteCollection)="deleteCollection(selectedCollection.node)"
|
||||
></app-vault-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="groupings">
|
||||
<div class="content">
|
||||
<div class="inner-content">
|
||||
<app-vault-filter
|
||||
#vaultFilter
|
||||
[activeFilter]="activeFilter"
|
||||
[searchText]="currentSearchText$ | async"
|
||||
(searchTextChanged)="filterSearchText($event)"
|
||||
(onEditFolder)="editFolder($event)"
|
||||
></app-vault-filter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div [ngClass]="{ 'col-6': isShowingCards, 'col-9': !isShowingCards }">
|
||||
<app-vault-header
|
||||
[filter]="filter"
|
||||
[loading]="refreshing && !performingInitialLoad"
|
||||
[organizations]="allOrganizations"
|
||||
[canCreateCollections]="canCreateCollections"
|
||||
[collection]="selectedCollection"
|
||||
(onAddCipher)="addCipher()"
|
||||
(onAddCollection)="addCollection()"
|
||||
(onAddFolder)="addFolder()"
|
||||
(onEditCollection)="editCollection(selectedCollection.node, $event.tab)"
|
||||
(onDeleteCollection)="deleteCollection(selectedCollection.node)"
|
||||
></app-vault-header>
|
||||
<app-callout type="warning" *ngIf="activeFilter.isDeleted" icon="bwi-exclamation-triangle">
|
||||
{{ trashCleanupWarning }}
|
||||
</app-callout>
|
||||
<app-vault-items
|
||||
[ciphers]="ciphers"
|
||||
[collections]="collections"
|
||||
[allCollections]="allCollections"
|
||||
[allOrganizations]="allOrganizations"
|
||||
[disabled]="refreshing"
|
||||
[showOwner]="true"
|
||||
[showCollections]="false"
|
||||
[showGroups]="false"
|
||||
[showPremiumFeatures]="canAccessPremium"
|
||||
[showBulkMove]="showBulkMove"
|
||||
[showBulkTrashOptions]="filter.type === 'trash'"
|
||||
[useEvents]="false"
|
||||
[cloneableOrganizationCiphers]="false"
|
||||
[showAdminActions]="false"
|
||||
(onEvent)="onVaultItemsEvent($event)"
|
||||
[showBulkEditCollectionAccess]="showBulkCollectionAccess$ | async"
|
||||
>
|
||||
</app-vault-items>
|
||||
<div
|
||||
*ngIf="performingInitialLoad"
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="isEmpty && !performingInitialLoad"
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
>
|
||||
<bit-icon [icon]="noItemIcon" aria-hidden="true"></bit-icon>
|
||||
<p>{{ "noItemsInList" | i18n }}</p>
|
||||
<button
|
||||
type="button"
|
||||
buttonType="primary"
|
||||
bitButton
|
||||
(click)="addCipher()"
|
||||
*ngIf="filter.type !== 'trash'"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newItem" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<app-low-kdf class="d-block mb-4" *ngIf="showLowKdf"> </app-low-kdf>
|
||||
<app-vault-onboarding [ciphers]="ciphers" (onAddCipher)="addCipher()"> </app-vault-onboarding>
|
||||
|
||||
<app-verify-email
|
||||
*ngIf="showVerifyEmail"
|
||||
class="d-block mb-4"
|
||||
(onVerified)="emailVerified($event)"
|
||||
></app-verify-email>
|
||||
|
||||
<div class="card border-warning mb-4" *ngIf="showBrowserOutdated">
|
||||
<div class="card-header bg-warning text-white">
|
||||
<i class="bwi bwi-exclamation-triangle bwi-fw" aria-hidden="true"></i>
|
||||
{{ "updateBrowser" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ "updateBrowserDesc" | i18n }}</p>
|
||||
<a
|
||||
class="btn btn-block btn-outline-secondary"
|
||||
target="_blank"
|
||||
href="https://browser-update.org/update-browser.html"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{{ "updateBrowser" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border-success mb-4" *ngIf="showPremiumCallout">
|
||||
<div class="card-header bg-success text-white">
|
||||
<i class="bwi bwi-star-f bwi-fw" aria-hidden="true"></i> {{ "goPremium" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ "premiumUpgradeUnlockFeatures" | i18n }}</p>
|
||||
<a
|
||||
class="btn btn-block btn-outline-secondary"
|
||||
routerLink="/settings/subscription/premium"
|
||||
>
|
||||
{{ "goPremium" | i18n }}
|
||||
</a>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="groupings">
|
||||
<div class="content">
|
||||
<div class="inner-content">
|
||||
<app-vault-filter
|
||||
#vaultFilter
|
||||
[activeFilter]="activeFilter"
|
||||
[searchText]="currentSearchText$ | async"
|
||||
(searchTextChanged)="filterSearchText($event)"
|
||||
(onEditFolder)="editFolder($event)"
|
||||
></app-vault-filter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div [ngClass]="{ 'col-6': isShowingCards, 'col-9': !isShowingCards }">
|
||||
<app-callout type="warning" *ngIf="activeFilter.isDeleted" icon="bwi-exclamation-triangle">
|
||||
{{ trashCleanupWarning }}
|
||||
</app-callout>
|
||||
<app-vault-items
|
||||
[ciphers]="ciphers"
|
||||
[collections]="collections"
|
||||
[allCollections]="allCollections"
|
||||
[allOrganizations]="allOrganizations"
|
||||
[disabled]="refreshing"
|
||||
[showOwner]="true"
|
||||
[showCollections]="false"
|
||||
[showGroups]="false"
|
||||
[showPremiumFeatures]="canAccessPremium"
|
||||
[showBulkMove]="showBulkMove"
|
||||
[showBulkTrashOptions]="filter.type === 'trash'"
|
||||
[useEvents]="false"
|
||||
[cloneableOrganizationCiphers]="false"
|
||||
[showAdminActions]="false"
|
||||
(onEvent)="onVaultItemsEvent($event)"
|
||||
[showBulkEditCollectionAccess]="showBulkCollectionAccess$ | async"
|
||||
>
|
||||
</app-vault-items>
|
||||
<div
|
||||
*ngIf="performingInitialLoad"
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="isEmpty && !performingInitialLoad"
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
>
|
||||
<bit-icon [icon]="noItemIcon" aria-hidden="true"></bit-icon>
|
||||
<p>{{ "noItemsInList" | i18n }}</p>
|
||||
<button
|
||||
type="button"
|
||||
buttonType="primary"
|
||||
bitButton
|
||||
(click)="addCipher()"
|
||||
*ngIf="filter.type !== 'trash'"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newItem" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<app-low-kdf class="d-block mb-4" *ngIf="showLowKdf"> </app-low-kdf>
|
||||
|
||||
<app-verify-email
|
||||
*ngIf="showVerifyEmail"
|
||||
class="d-block mb-4"
|
||||
(onVerified)="emailVerified($event)"
|
||||
></app-verify-email>
|
||||
|
||||
<div class="card border-warning mb-4" *ngIf="showBrowserOutdated">
|
||||
<div class="card-header bg-warning text-white">
|
||||
<i class="bwi bwi-exclamation-triangle bwi-fw" aria-hidden="true"></i>
|
||||
{{ "updateBrowser" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ "updateBrowserDesc" | i18n }}</p>
|
||||
<a
|
||||
class="btn btn-block btn-outline-secondary"
|
||||
target="_blank"
|
||||
href="https://browser-update.org/update-browser.html"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{{ "updateBrowser" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border-success mb-4" *ngIf="showPremiumCallout">
|
||||
<div class="card-header bg-success text-white">
|
||||
<i class="bwi bwi-star-f bwi-fw" aria-hidden="true"></i> {{ "goPremium" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ "premiumUpgradeUnlockFeatures" | i18n }}</p>
|
||||
<a class="btn btn-block btn-outline-secondary" routerLink="/settings/subscription/premium">
|
||||
{{ "goPremium" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #attachments></ng-template>
|
||||
<ng-template #folderAddEdit></ng-template>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SearchModule } from "@bitwarden/components";
|
||||
|
||||
import { VaultFilterService as VaultFilterServiceAbstraction } from "../../individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
import { VaultFilterSharedModule } from "../../individual-vault/vault-filter/shared/vault-filter-shared.module";
|
||||
|
||||
@ -7,7 +9,7 @@ import { VaultFilterComponent } from "./vault-filter.component";
|
||||
import { VaultFilterService } from "./vault-filter.service";
|
||||
|
||||
@NgModule({
|
||||
imports: [VaultFilterSharedModule],
|
||||
imports: [VaultFilterSharedModule, SearchModule],
|
||||
declarations: [VaultFilterComponent],
|
||||
exports: [VaultFilterComponent],
|
||||
providers: [
|
||||
|
||||
@ -1,88 +1,77 @@
|
||||
<div class="tw-mb-4 tw-flex tw-items-start tw-justify-between">
|
||||
<div>
|
||||
<bit-breadcrumbs *ngIf="showBreadcrumbs">
|
||||
<app-header [title]="title" [icon]="icon">
|
||||
<bit-breadcrumbs *ngIf="showBreadcrumbs" slot="breadcrumbs">
|
||||
<bit-breadcrumb
|
||||
[route]="[]"
|
||||
[queryParams]="{ organizationId: organization.id, collectionId: null }"
|
||||
queryParamsHandling="merge"
|
||||
>
|
||||
{{ organization.name }}
|
||||
<span *ngIf="!organization.flexibleCollections">
|
||||
{{ "vault" | i18n | lowercase }}
|
||||
</span>
|
||||
<span *ngIf="organization.flexibleCollections">
|
||||
{{ "collections" | i18n | lowercase }}
|
||||
</span>
|
||||
</bit-breadcrumb>
|
||||
|
||||
<ng-container>
|
||||
<bit-breadcrumb
|
||||
*ngFor="let collection of collections"
|
||||
icon="bwi-collection"
|
||||
[route]="[]"
|
||||
[queryParams]="{ organizationId: organization.id, collectionId: null }"
|
||||
[queryParams]="{ collectionId: collection.id }"
|
||||
queryParamsHandling="merge"
|
||||
>
|
||||
{{ organization.name }}
|
||||
<span *ngIf="!organization.flexibleCollections">
|
||||
{{ "vault" | i18n | lowercase }}
|
||||
</span>
|
||||
<span *ngIf="organization.flexibleCollections">
|
||||
{{ "collections" | i18n | lowercase }}
|
||||
</span>
|
||||
{{ collection.name }}
|
||||
</bit-breadcrumb>
|
||||
<ng-container>
|
||||
<bit-breadcrumb
|
||||
*ngFor="let collection of collections"
|
||||
icon="bwi-collection"
|
||||
[route]="[]"
|
||||
[queryParams]="{ collectionId: collection.id }"
|
||||
queryParamsHandling="merge"
|
||||
</ng-container>
|
||||
</bit-breadcrumbs>
|
||||
|
||||
<ng-container slot="title-suffix">
|
||||
<ng-container *ngIf="collection != null && (canEditCollection || canDeleteCollection)">
|
||||
<button
|
||||
bitIconButton="bwi-angle-down"
|
||||
[bitMenuTriggerFor]="editCollectionMenu"
|
||||
size="small"
|
||||
type="button"
|
||||
aria-haspopup="true"
|
||||
></button>
|
||||
<bit-menu #editCollectionMenu>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection"
|
||||
bitMenuItem
|
||||
(click)="editCollection(CollectionDialogTabType.Info)"
|
||||
>
|
||||
{{ collection.name }}
|
||||
</bit-breadcrumb>
|
||||
</ng-container>
|
||||
</bit-breadcrumbs>
|
||||
<h1 class="tw-mb-0 tw-mt-1 tw-flex tw-items-center tw-space-x-2">
|
||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
||||
{{ "editInfo" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection"
|
||||
bitMenuItem
|
||||
(click)="editCollection(CollectionDialogTabType.Access)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "access" | i18n }}
|
||||
</button>
|
||||
<button type="button" *ngIf="canDeleteCollection" bitMenuItem (click)="deleteCollection()">
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
<small *ngIf="loading">
|
||||
<i
|
||||
*ngIf="filter.collectionId !== undefined"
|
||||
class="bwi bwi-collection"
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{{ title }}</span>
|
||||
<ng-container *ngIf="collection != null && (canEditCollection || canDeleteCollection)">
|
||||
<button
|
||||
bitIconButton="bwi-angle-down"
|
||||
[bitMenuTriggerFor]="editCollectionMenu"
|
||||
size="small"
|
||||
type="button"
|
||||
aria-haspopup
|
||||
></button>
|
||||
<bit-menu #editCollectionMenu>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection"
|
||||
bitMenuItem
|
||||
(click)="editCollection(CollectionDialogTabType.Info)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
||||
{{ "editInfo" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection"
|
||||
bitMenuItem
|
||||
(click)="editCollection(CollectionDialogTabType.Access)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "access" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canDeleteCollection"
|
||||
bitMenuItem
|
||||
(click)="deleteCollection()"
|
||||
>
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
<small *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned" class="tw-shrink-0">
|
||||
<div *ngIf="organization?.canCreateNewCollections" appListDropdown>
|
||||
@ -118,4 +107,4 @@
|
||||
{{ "newItem" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</app-header>
|
||||
|
||||
@ -80,6 +80,10 @@ export class VaultHeaderComponent {
|
||||
return `${this.organization?.name} ${headerType}`;
|
||||
}
|
||||
|
||||
get icon() {
|
||||
return this.filter.collectionId !== undefined ? "bwi-collection" : "";
|
||||
}
|
||||
|
||||
protected get showBreadcrumbs() {
|
||||
return this.filter.collectionId !== undefined && this.filter.collectionId !== All;
|
||||
}
|
||||
|
||||
@ -1,127 +1,121 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="groupings">
|
||||
<div class="content">
|
||||
<div class="inner-content">
|
||||
<app-organization-vault-filter
|
||||
#vaultFilter
|
||||
[organization]="organization"
|
||||
[activeFilter]="activeFilter"
|
||||
[searchText]="currentSearchText$ | async"
|
||||
(searchTextChanged)="filterSearchText($event)"
|
||||
></app-organization-vault-filter>
|
||||
</div>
|
||||
<app-org-vault-header
|
||||
[filter]="filter"
|
||||
[loading]="refreshing"
|
||||
[organization]="organization"
|
||||
[collection]="selectedCollection"
|
||||
(onAddCipher)="addCipher()"
|
||||
(onAddCollection)="addCollection()"
|
||||
(onEditCollection)="editCollection(selectedCollection.node, $event.tab)"
|
||||
(onDeleteCollection)="deleteCollection(selectedCollection.node)"
|
||||
></app-org-vault-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="groupings">
|
||||
<div class="content">
|
||||
<div class="inner-content">
|
||||
<app-organization-vault-filter
|
||||
#vaultFilter
|
||||
[organization]="organization"
|
||||
[activeFilter]="activeFilter"
|
||||
[searchText]="currentSearchText$ | async"
|
||||
(searchTextChanged)="filterSearchText($event)"
|
||||
></app-organization-vault-filter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<app-org-vault-header
|
||||
[filter]="filter"
|
||||
[loading]="refreshing"
|
||||
[organization]="organization"
|
||||
[collection]="selectedCollection"
|
||||
(onAddCipher)="addCipher()"
|
||||
(onAddCollection)="addCollection()"
|
||||
(onEditCollection)="editCollection(selectedCollection.node, $event.tab)"
|
||||
(onDeleteCollection)="deleteCollection(selectedCollection.node)"
|
||||
></app-org-vault-header>
|
||||
<app-callout
|
||||
type="warning"
|
||||
*ngIf="activeFilter.isDeleted"
|
||||
icon="bwi bwi-exclamation-triangle"
|
||||
>
|
||||
{{ trashCleanupWarning }}
|
||||
</app-callout>
|
||||
<app-vault-items
|
||||
[ciphers]="ciphers"
|
||||
[collections]="collections"
|
||||
[allCollections]="allCollections"
|
||||
[allOrganizations]="organization ? [organization] : []"
|
||||
[allGroups]="allGroups"
|
||||
[disabled]="loading"
|
||||
[showOwner]="false"
|
||||
[showPermissionsColumn]="true"
|
||||
[showCollections]="filter.type !== undefined"
|
||||
[showGroups]="
|
||||
organization?.useGroups &&
|
||||
((filter.type === undefined && filter.collectionId === undefined) ||
|
||||
filter.collectionId !== undefined)
|
||||
"
|
||||
[showPremiumFeatures]="organization?.useTotp"
|
||||
[showBulkMove]="false"
|
||||
[showBulkTrashOptions]="filter.type === 'trash'"
|
||||
[useEvents]="organization?.useEvents"
|
||||
[cloneableOrganizationCiphers]="true"
|
||||
[showAdminActions]="true"
|
||||
(onEvent)="onVaultItemsEvent($event)"
|
||||
[showBulkEditCollectionAccess]="
|
||||
(showBulkEditCollectionAccess$ | async) && organization?.flexibleCollections
|
||||
"
|
||||
[viewingOrgVault]="true"
|
||||
>
|
||||
</app-vault-items>
|
||||
<ng-container *ngIf="!flexibleCollectionsV1Enabled">
|
||||
<div
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
*ngIf="showMissingCollectionPermissionMessage"
|
||||
>
|
||||
<bit-icon [icon]="noItemIcon" aria-hidden="true"></bit-icon>
|
||||
<p>{{ "noPermissionToViewAllCollectionItems" | i18n }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
*ngIf="isEmpty && !showMissingCollectionPermissionMessage && !performingInitialLoad"
|
||||
>
|
||||
<bit-icon [icon]="noItemIcon" aria-hidden="true"></bit-icon>
|
||||
<p>{{ "noItemsInList" | i18n }}</p>
|
||||
<button
|
||||
type="button"
|
||||
buttonType="primary"
|
||||
bitButton
|
||||
(click)="addCipher()"
|
||||
*ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newItem" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="flexibleCollectionsV1Enabled && !performingInitialLoad && isEmpty">
|
||||
<bit-no-items *ngIf="!showCollectionAccessRestricted">
|
||||
<span slot="title" class="tw-mt-4 tw-block">{{ "noItemsInList" | i18n }}</span>
|
||||
<button
|
||||
slot="button"
|
||||
bitButton
|
||||
(click)="addCipher()"
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
*ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-plus"></i> {{ "newItem" | i18n }}
|
||||
</button>
|
||||
</bit-no-items>
|
||||
<collection-access-restricted
|
||||
*ngIf="showCollectionAccessRestricted"
|
||||
[canEdit]="selectedCollection != null && selectedCollection.node.canEdit(organization)"
|
||||
(editInfoClicked)="editCollection(selectedCollection.node, CollectionDialogTabType.Info)"
|
||||
>
|
||||
</collection-access-restricted>
|
||||
</ng-container>
|
||||
<div
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
*ngIf="performingInitialLoad"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #attachments></ng-template>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
<ng-template #collectionsModal></ng-template>
|
||||
<div class="col-9">
|
||||
<app-callout type="warning" *ngIf="activeFilter.isDeleted" icon="bwi bwi-exclamation-triangle">
|
||||
{{ trashCleanupWarning }}
|
||||
</app-callout>
|
||||
<app-vault-items
|
||||
[ciphers]="ciphers"
|
||||
[collections]="collections"
|
||||
[allCollections]="allCollections"
|
||||
[allOrganizations]="organization ? [organization] : []"
|
||||
[allGroups]="allGroups"
|
||||
[disabled]="loading"
|
||||
[showOwner]="false"
|
||||
[showPermissionsColumn]="true"
|
||||
[showCollections]="filter.type !== undefined"
|
||||
[showGroups]="
|
||||
organization?.useGroups &&
|
||||
((filter.type === undefined && filter.collectionId === undefined) ||
|
||||
filter.collectionId !== undefined)
|
||||
"
|
||||
[showPremiumFeatures]="organization?.useTotp"
|
||||
[showBulkMove]="false"
|
||||
[showBulkTrashOptions]="filter.type === 'trash'"
|
||||
[useEvents]="organization?.useEvents"
|
||||
[cloneableOrganizationCiphers]="true"
|
||||
[showAdminActions]="true"
|
||||
(onEvent)="onVaultItemsEvent($event)"
|
||||
[showBulkEditCollectionAccess]="
|
||||
(showBulkEditCollectionAccess$ | async) && organization?.flexibleCollections
|
||||
"
|
||||
>
|
||||
</app-vault-items>
|
||||
<ng-container *ngIf="!flexibleCollectionsV1Enabled">
|
||||
<div
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
*ngIf="showMissingCollectionPermissionMessage"
|
||||
>
|
||||
<bit-icon [icon]="noItemIcon" aria-hidden="true"></bit-icon>
|
||||
<p>{{ "noPermissionToViewAllCollectionItems" | i18n }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
*ngIf="isEmpty && !showMissingCollectionPermissionMessage && !performingInitialLoad"
|
||||
>
|
||||
<bit-icon [icon]="noItemIcon" aria-hidden="true"></bit-icon>
|
||||
<p>{{ "noItemsInList" | i18n }}</p>
|
||||
<button
|
||||
type="button"
|
||||
buttonType="primary"
|
||||
bitButton
|
||||
(click)="addCipher()"
|
||||
*ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newItem" | i18n }}
|
||||
</button>
|
||||
</div></ng-container
|
||||
>
|
||||
<ng-container *ngIf="flexibleCollectionsV1Enabled && !performingInitialLoad && isEmpty">
|
||||
<bit-no-items *ngIf="!showCollectionAccessRestricted">
|
||||
<span slot="title" class="tw-mt-4 tw-block">{{ "noItemsInList" | i18n }}</span>
|
||||
<button
|
||||
slot="button"
|
||||
bitButton
|
||||
(click)="addCipher()"
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
*ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-plus"></i> {{ "newItem" | i18n }}
|
||||
</button>
|
||||
</bit-no-items>
|
||||
<collection-access-restricted
|
||||
*ngIf="showCollectionAccessRestricted"
|
||||
[canEdit]="selectedCollection != null && selectedCollection.node.canEdit(organization)"
|
||||
(editInfoClicked)="editCollection(selectedCollection.node, CollectionDialogTabType.Info)"
|
||||
>
|
||||
</collection-access-restricted>
|
||||
</ng-container>
|
||||
<div
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
*ngIf="performingInitialLoad"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #attachments></ng-template>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
<ng-template #collectionsModal></ng-template>
|
||||
|
||||
@ -1472,13 +1472,6 @@
|
||||
"faviconDesc": {
|
||||
"message": "Show a recognizable image next to each login."
|
||||
},
|
||||
"enableFullWidth": {
|
||||
"message": "Display full width layout",
|
||||
"description": "Allows scaling the web vault UI's width"
|
||||
},
|
||||
"enableFullWidthDesc": {
|
||||
"message": "Allow the web vault to expand the full width of the browser window."
|
||||
},
|
||||
"default": {
|
||||
"message": "Default"
|
||||
},
|
||||
@ -4950,6 +4943,9 @@
|
||||
"newClientOrganizationDesc": {
|
||||
"message": "Create a new client organization that will be associated with you as the Provider. You will be able to access and manage this organization."
|
||||
},
|
||||
"newClient": {
|
||||
"message": "New client"
|
||||
},
|
||||
"addExistingOrganization": {
|
||||
"message": "Add existing organization"
|
||||
},
|
||||
@ -7587,5 +7583,11 @@
|
||||
},
|
||||
"freeForOneYear": {
|
||||
"message": "Free for 1 year"
|
||||
},
|
||||
"newWebApp": {
|
||||
"message": "Welcome to the new and improved web app. Learn more about what’s changed."
|
||||
},
|
||||
"releaseBlog": {
|
||||
"message": "Read release blog"
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,14 +90,6 @@ img.logo-themed {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
padding: 40px 0 40px 0;
|
||||
@include themify($themes) {
|
||||
border-top: 1px solid themed("separator");
|
||||
}
|
||||
}
|
||||
|
||||
hr,
|
||||
.dropdown-divider {
|
||||
@include themify($themes) {
|
||||
|
||||
@ -199,17 +199,3 @@ button.no-btn {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
button.header-expandable {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
margin-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* special case for Send options */
|
||||
h3 button.header-expandable .bwi {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@ -1,104 +0,0 @@
|
||||
.navbar {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
@include themify($themes) {
|
||||
background-color: themed("navBackground");
|
||||
}
|
||||
|
||||
&.nav-background-alt {
|
||||
@include themify($themes) {
|
||||
background-color: themed("navBackgroundAlt");
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
> .nav-link {
|
||||
@include themify($themes) {
|
||||
font-weight: themed("navWeight");
|
||||
}
|
||||
}
|
||||
&.active > .nav-link {
|
||||
@include themify($themes) {
|
||||
font-weight: themed("navActiveWeight");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
margin-bottom: -20px;
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active {
|
||||
@include themify($themes) {
|
||||
background: themed("navActiveBackground");
|
||||
border-color: themed("borderColor");
|
||||
}
|
||||
}
|
||||
|
||||
.org-nav {
|
||||
height: 100px;
|
||||
min-height: 100px;
|
||||
@include themify($themes) {
|
||||
background-color: themed("navOrgBackgroundColor");
|
||||
border-bottom: 1px solid themed("borderColor");
|
||||
color: themed("textColor");
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.org-name {
|
||||
line-height: 1;
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
span {
|
||||
display: block;
|
||||
font-size: $font-size-lg;
|
||||
@include themify($themes) {
|
||||
color: themed("textHeadingColor");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabbed-nav {
|
||||
@include themify($themes) {
|
||||
border-bottom: 1px solid themed("borderColor");
|
||||
color: themed("textColor");
|
||||
}
|
||||
}
|
||||
|
||||
.org-nav,
|
||||
.tabbed-nav {
|
||||
.nav-tabs {
|
||||
border-bottom: none;
|
||||
|
||||
a {
|
||||
&:not(.active) {
|
||||
border-color: transparent;
|
||||
@include themify($themes) {
|
||||
color: themed("textColor");
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: bold;
|
||||
padding-top: calc(#{$nav-link-padding-y} - 2px);
|
||||
@include themify($themes) {
|
||||
border-top: 3px solid themed("primary");
|
||||
border-bottom: 1px solid themed("backgroundColor");
|
||||
color: themed("linkColor");
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
@include themify($themes) {
|
||||
color: themed("inputDisabledColor");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,7 @@
|
||||
@import "~bootstrap/scss/_input-group";
|
||||
// @import "~bootstrap/scss/_custom-forms";
|
||||
@import "~bootstrap/scss/_nav";
|
||||
@import "~bootstrap/scss/_navbar";
|
||||
// @import "~bootstrap/scss/_navbar";
|
||||
@import "~bootstrap/scss/_card";
|
||||
// @import "~bootstrap/scss/_breadcrumb";
|
||||
// @import "~bootstrap/scss/_pagination";
|
||||
@ -50,7 +50,6 @@
|
||||
@import "./callouts";
|
||||
@import "./cards";
|
||||
@import "./forms";
|
||||
@import "./navigation";
|
||||
@import "./modals";
|
||||
@import "./pages";
|
||||
@import "./plugins";
|
||||
|
||||
@ -3,3 +3,8 @@
|
||||
@tailwind utilities;
|
||||
|
||||
@import "../../../../libs/components/src/tw-theme.css";
|
||||
|
||||
/* Prevent page from jumping when showing scrollbar */
|
||||
html {
|
||||
margin-right: calc(-100vw + 100%);
|
||||
}
|
||||
|
||||
@ -34,7 +34,6 @@ $small-font-size: 90%;
|
||||
$font-size-lg: 1.15rem;
|
||||
$code-font-size: 100%;
|
||||
|
||||
$navbar-padding-y: 0.75rem;
|
||||
$grid-gutter-width: 20px;
|
||||
$card-spacer-y: 0.6rem;
|
||||
|
||||
@ -49,13 +48,6 @@ $dropdown-link-active-color: $dropdown-link-color;
|
||||
$dropdown-link-active-bg: rgba(#000000, 0.1);
|
||||
$dropdown-item-padding-x: 1rem;
|
||||
|
||||
$navbar-brand-font-size: 35px;
|
||||
$navbar-brand-height: 35px;
|
||||
$navbar-brand-padding-y: 0;
|
||||
$navbar-dark-color: rgba($white, 0.7);
|
||||
$navbar-dark-hover-color: rgba($white, 0.9);
|
||||
$navbar-nav-link-padding-x: 0.8rem;
|
||||
|
||||
$input-bg: #fbfbfb;
|
||||
$input-focus-bg: $white;
|
||||
$input-disabled-bg: #e0e0e0;
|
||||
@ -197,12 +189,6 @@ $themes: (
|
||||
loadingSvg: url("../images/loading.svg"),
|
||||
logoSuffix: "dark",
|
||||
mfaLogoSuffix: ".png",
|
||||
navActiveBackground: $white,
|
||||
navActiveWeight: 600,
|
||||
navBackground: $primary,
|
||||
navBackgroundAlt: $secondary-alt,
|
||||
navOrgBackgroundColor: #fbfbfb,
|
||||
navWeight: 600,
|
||||
pwStrengthBackground: #e9ecef,
|
||||
separator: $secondary,
|
||||
separatorHr: rgb(0, 0, 0, 0.1),
|
||||
@ -306,12 +292,6 @@ $themes: (
|
||||
loadingSvg: url("../images/loading-white.svg"),
|
||||
logoSuffix: "white",
|
||||
mfaLogoSuffix: "-w.png",
|
||||
navActiveBackground: $darkDarkBlue2,
|
||||
navActiveWeight: 600,
|
||||
navBackground: $darkDarkBlue1,
|
||||
navBackgroundAlt: $darkDarkBlue1,
|
||||
navOrgBackgroundColor: #161c26,
|
||||
navWeight: 400,
|
||||
pwStrengthBackground: $darkBlue2,
|
||||
separator: $darkBlue1,
|
||||
separatorHr: $darkBlue1,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user