mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-25 12:15:18 +01:00
[SG-220] End User Vault Refresh (#1640)
* Add premium badge component (#1525) * [Vault Refresh] Nav update and Options -> Preferences (#1530) * Update jslib * [End User Vault Refresh] Security sub-page (#1538) * [End User Vault Refresh] Security section * Updated routing module * Update routing for change-password * Updated buttons of all modified classes // imported button module * Converted modified class to use bit-callout * removed comments * Update small button to current cl button * Update jslib and consequential updates * [End User Vault Refresh] Vault - remove Org and Provider cards (#1529) * Update reports page (#1536) * [End User Vault Refresh] Organizations - updated nav and route permissions (#1551) * Add Organizations link to navbar * Update route permissions and guards * Use NavigationPermissionsService to unify route permissions * Rename "My Vault" to "Vaults" (#1569) * [euvr] Adjust Vault width based on card visibility (#1588) * [SG-31 End User Vault Refresh] Account Menu updates (#1596) * Add menuModule * Use bit-menu for account menu * Fix styling, replace CSS with TW * Change out bootstrap styling * Fix styling * Fix styling * Rename My Account to Account Settings * WIP use Avatar for account menu * Revert "WIP use Avatar for account menu" This reverts commit d58bea4874d94d5cdf5a96f7047623b1e0c839b7. * Update jslib from feature branch * [End User Vault Refresh] SG-16: Organization filters (#1595) * [feature] Base implementation of EUVR filter changes * [refactor] Relocated vault-filters to app/modules * [refactor] Reuse vault-filters component for organizations * [refactor] Remove unused org filter component * [bug] .gitmodules branch change * [bug] Load organization filters after sync during login * [refactor] Introduce a SharedModule * [refactor] Created a home for loose components * [refactor] Convert VaultComponent and OrgVaultComponent into a pair of modules * [refactor] Implement <bit-menu> for organization filter actions * [feature] Improve a11y standards of the vault filters module * [bug] Recreate package-lock.json * Fix build issue * [bug] Remove duplicate this.go() call * [fix] Use correct filter-buttons class Co-authored-by: addison <addisonbeck1@gmail.com> Co-authored-by: Hinton <oscar@oscarhinton.com> * [SG-32] Add Ownership badge to vault items (#1623) * [feature] Base implementation of EUVR filter changes * [refactor] Relocated vault-filters to app/modules * [refactor] Reuse vault-filters component for organizations * [refactor] Remove unused org filter component * [bug] .gitmodules branch change * [bug] Load organization filters after sync during login * [refactor] Introduce a SharedModule * [refactor] Created a home for loose components * [refactor] Convert VaultComponent and OrgVaultComponent into a pair of modules * [refactor] Implement <bit-menu> for organization filter actions * [feature] Improve a11y standards of the vault filters module * [bug] Recreate package-lock.json * Fix build issue * [bug] Remove duplicate this.go() call * Add organization owner badge to vault items * Fix capitalization * Re-organize new components into modules * Use tailwind css class Co-authored-by: addison <addisonbeck1@gmail.com> Co-authored-by: Hinton <oscar@oscarhinton.com> * [EUVR] Merge master into feature branch (#1637) * Update jslib (#1602) * Update jslib * Update name of UserVerificationComponent * Bumped version to 2.28.0 (#1603) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * [EC-161] Bump braintree (#1606) * [PS-211] [PS-212] Make Generator page accessible (#1607) * Fix grouping of radiobutton inputs * Add role=radiogroup * Add aria-labelledBy to radio button groups * Add reorganization notice (#1610) * Add aria attributes to password gen options (#1611) * [EC-143] [BEEEP] Allow linking to ciphers (#1579) Co-authored-by: Matt Gibson <mgibson@bitwarden.com> * Fix login sponsorship redirect (#1620) * Contribution Documentation edits (#1599) Making corrections to the mobile contributions doc: Update Crowdin contact from Kyle to dwbit. Update 'User-to-User Support' forum category to 'Ask the Bitwarden Community' * Add description for the A-Z & a-z items (#1615) * Add description for reports message (#1600) Add "Vault Health Reports can be used to evaluate the security of your Bitwarden Personal or Organization Vault" description to the source string, "Identify and close security gaps in your online accounts by clicking the reports below." * [PS-301] Load OssModule from BitwardenLicense (#1626) * Bumped version to 2.28.1 (#1629) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * [EC-154] [BEEEP] Remove factory providers in Angular DI (#1609) * use InjectionTokens * Use InitService * PS-79 Updated two-factor component to to align to jslib change to send the deviceId on 2fa email resend code (#1624) * [PS-74] Fix user authentication state checks (#1632) * Update to use new authStatus method * Delete unused services and import * update jslib * [PS-381] Fix locale being empty when not configuring a language (#1631) * Forwarded email providers to username generator (#1628) * forwarded emails * firefox relay * remove firefox relay * update jslib ref * remove dupe logService * Update localization description for 'random' (#1633) Adding description string for 'random' * DEVOPS-758 - Move Web deploy from GitHub Pages to CloudFlare Pages (#1627) * Update jslib * Run npm i after merge with master * Update name of UserVerificationComponent * Fix lazy loading of routing modules * Routing modules should have routing in their name * Revert "Fix lazy loading of routing modules" This reverts commit 59d4e6e06caf54692db8662fb4ed799dc2836dc3. * Do not eagerly load feature modules Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton <oscar@oscarhinton.com> Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> Co-authored-by: Matt Gibson <mgibson@bitwarden.com> Co-authored-by: dwbit <98768076+dwbit@users.noreply.github.com> Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com> Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> * Do not render org options menu until loaded (#1638) * [SG-31 End User Vault Refresh] Update cipher options menu (#1593) * Update Vault cipher option menus * Update Send list to use same style * [SG-207] [EUVR] Remove Organizations from Settings page (#1619) * [fix] Cut off overflow text for link buttons (#1639) * [SG-225] Remove BaseGuard (#1641) * [SG-34 End User Vault Refresh] Organization Switcher (#1550) * [euvr] Subscription/Billing updates (#1576) * [euvr] Subscription changes * Revert testing bang * Removed final instance of getUserBilling * Moved to feature/endUserVaultRefresh remote branch and updated to latest * Removed org-billing changes * Updated premium component header * Updated stateservice path * Updated billing component name * Reverting org-billing decouple * Using tailwind classes for CL objects * Added TODO * Removed divider for components within new tab nav * Update jslib/add components to loose-components module * Updated routing lazy load module name to match existing pattern * Fixed bug with redirect // Added button type // Removed headers for tabbed pages * Revert changes to .gitmodules * [dep] Update jslib Co-authored-by: Oscar Hinton <oscar@oscarhinton.com> Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com> Co-authored-by: addison <addisonbeck1@gmail.com> Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> Co-authored-by: Matt Gibson <mgibson@bitwarden.com> Co-authored-by: dwbit <98768076+dwbit@users.noreply.github.com> Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com> Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
This commit is contained in:
parent
bc054236ad
commit
468007a984
@ -1,4 +1,5 @@
|
||||
import { DragDropModule } from "@angular/cdk/drag-drop";
|
||||
import { OverlayModule } from "@angular/cdk/overlay";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
@ -20,6 +21,7 @@ import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-tim
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
OverlayModule,
|
||||
OssModule,
|
||||
JslibModule,
|
||||
BrowserAnimationsModule,
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
||||
import { AuthGuard } from "jslib-angular/guards/auth.guard";
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
|
||||
import { OrganizationLayoutComponent } from "src/app/layouts/organization-layout.component";
|
||||
import { PermissionsGuard } from "src/app/organizations/guards/permissions.guard";
|
||||
import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component";
|
||||
import { ManageComponent } from "src/app/organizations/manage/manage.component";
|
||||
import { OrganizationGuardService } from "src/app/services/organization-guard.service";
|
||||
import { OrganizationTypeGuardService } from "src/app/services/organization-type-guard.service";
|
||||
import { NavigationPermissionsService } from "src/app/organizations/services/navigation-permissions.service";
|
||||
|
||||
import { SsoComponent } from "./manage/sso.component";
|
||||
|
||||
@ -15,24 +15,15 @@ const routes: Routes = [
|
||||
{
|
||||
path: "organizations/:organizationId",
|
||||
component: OrganizationLayoutComponent,
|
||||
canActivate: [AuthGuardService, OrganizationGuardService],
|
||||
canActivate: [AuthGuard, PermissionsGuard],
|
||||
children: [
|
||||
{
|
||||
path: "manage",
|
||||
component: ManageComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
permissions: [
|
||||
Permissions.CreateNewCollections,
|
||||
Permissions.EditAnyCollection,
|
||||
Permissions.DeleteAnyCollection,
|
||||
Permissions.EditAssignedCollections,
|
||||
Permissions.DeleteAssignedCollections,
|
||||
Permissions.AccessEventLogs,
|
||||
Permissions.ManageGroups,
|
||||
Permissions.ManageUsers,
|
||||
Permissions.ManagePolicies,
|
||||
Permissions.ManageSso,
|
||||
NavigationPermissionsService.getPermissions("manage").concat(Permissions.ManageSso),
|
||||
],
|
||||
},
|
||||
children: [
|
||||
|
@ -5,7 +5,7 @@ import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
|
||||
@Injectable()
|
||||
export class ProviderTypeGuardService implements CanActivate {
|
||||
export class PermissionsGuard implements CanActivate {
|
||||
constructor(private providerService: ProviderService, private router: Router) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot) {
|
@ -6,7 +6,7 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
|
||||
@Injectable()
|
||||
export class ProviderGuardService implements CanActivate {
|
||||
export class ProviderGuard implements CanActivate {
|
||||
constructor(
|
||||
private router: Router,
|
||||
private platformUtilsService: PlatformUtilsService,
|
@ -1,7 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
||||
import { AuthGuard } from "jslib-angular/guards/auth.guard";
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
|
||||
import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
|
||||
@ -9,13 +9,13 @@ import { ProvidersComponent } from "src/app/providers/providers.component";
|
||||
|
||||
import { ClientsComponent } from "./clients/clients.component";
|
||||
import { CreateOrganizationComponent } from "./clients/create-organization.component";
|
||||
import { PermissionsGuard } from "./guards/provider-type.guard";
|
||||
import { ProviderGuard } from "./guards/provider.guard";
|
||||
import { AcceptProviderComponent } from "./manage/accept-provider.component";
|
||||
import { EventsComponent } from "./manage/events.component";
|
||||
import { ManageComponent } from "./manage/manage.component";
|
||||
import { PeopleComponent } from "./manage/people.component";
|
||||
import { ProvidersLayoutComponent } from "./providers-layout.component";
|
||||
import { ProviderGuardService } from "./services/provider-guard.service";
|
||||
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
|
||||
import { AccountComponent } from "./settings/account.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { SetupProviderComponent } from "./setup/setup-provider.component";
|
||||
@ -24,7 +24,7 @@ import { SetupComponent } from "./setup/setup.component";
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
canActivate: [AuthGuardService],
|
||||
canActivate: [AuthGuard],
|
||||
component: ProvidersComponent,
|
||||
},
|
||||
{
|
||||
@ -45,7 +45,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
canActivate: [AuthGuardService],
|
||||
canActivate: [AuthGuard],
|
||||
children: [
|
||||
{
|
||||
path: "setup",
|
||||
@ -54,7 +54,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: ":providerId",
|
||||
component: ProvidersLayoutComponent,
|
||||
canActivate: [ProviderGuardService],
|
||||
canActivate: [ProviderGuard],
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "clients" },
|
||||
{ path: "clients/create", component: CreateOrganizationComponent },
|
||||
@ -71,7 +71,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: "people",
|
||||
component: PeopleComponent,
|
||||
canActivate: [ProviderTypeGuardService],
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "people",
|
||||
permissions: [Permissions.ManageUsers],
|
||||
@ -80,7 +80,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: "events",
|
||||
component: EventsComponent,
|
||||
canActivate: [ProviderTypeGuardService],
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "eventLogs",
|
||||
permissions: [Permissions.AccessEventLogs],
|
||||
@ -100,7 +100,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: "account",
|
||||
component: AccountComponent,
|
||||
canActivate: [ProviderTypeGuardService],
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "myProvider",
|
||||
permissions: [Permissions.ManageProvider],
|
||||
|
@ -10,6 +10,8 @@ import { OssModule } from "src/app/oss.module";
|
||||
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
||||
import { ClientsComponent } from "./clients/clients.component";
|
||||
import { CreateOrganizationComponent } from "./clients/create-organization.component";
|
||||
import { PermissionsGuard } from "./guards/provider-type.guard";
|
||||
import { ProviderGuard } from "./guards/provider.guard";
|
||||
import { AcceptProviderComponent } from "./manage/accept-provider.component";
|
||||
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
|
||||
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
|
||||
@ -19,8 +21,6 @@ import { PeopleComponent } from "./manage/people.component";
|
||||
import { UserAddEditComponent } from "./manage/user-add-edit.component";
|
||||
import { ProvidersLayoutComponent } from "./providers-layout.component";
|
||||
import { ProvidersRoutingModule } from "./providers-routing.module";
|
||||
import { ProviderGuardService } from "./services/provider-guard.service";
|
||||
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
|
||||
import { WebProviderService } from "./services/webProvider.service";
|
||||
import { AccountComponent } from "./settings/account.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
@ -46,7 +46,7 @@ import { SetupComponent } from "./setup/setup.component";
|
||||
SetupProviderComponent,
|
||||
UserAddEditComponent,
|
||||
],
|
||||
providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
|
||||
providers: [WebProviderService, ProviderGuard, PermissionsGuard],
|
||||
})
|
||||
export class ProvidersModule {
|
||||
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {
|
||||
|
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit 2e2849b4def0534f3f72b7a84c3183ab0b1589f2
|
||||
Subproject commit 141ade3c381cb9ec8e89f15061b92330cb32d403
|
3925
package-lock.json
generated
3925
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -98,6 +98,7 @@
|
||||
"jszip": "^3.7.1",
|
||||
"ngx-infinite-scroll": "^10.0.1",
|
||||
"ngx-toastr": "14.1.4",
|
||||
"node-forge": "^1.3.1",
|
||||
"popper.js": "1.16.1",
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "^7.4.0",
|
||||
|
68
src/app/components/organization-switcher.component.html
Normal file
68
src/app/components/organization-switcher.component.html
Normal file
@ -0,0 +1,68 @@
|
||||
<div *ngIf="loaded && activeOrganization != null" class="tw-flex">
|
||||
<button
|
||||
class="tw-flex tw-items-center tw-bg-background-alt tw-border-none"
|
||||
type="button"
|
||||
id="pickerButton"
|
||||
[appA11yTitle]="'organizationPicker' | i18n"
|
||||
[bitMenuTriggerFor]="orgPickerMenu"
|
||||
>
|
||||
<app-avatar
|
||||
[data]="activeOrganization.name"
|
||||
size="45"
|
||||
[circle]="true"
|
||||
[dynamic]="true"
|
||||
></app-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-border tw-border-solid tw-rounded tw-border-danger-500 tw-text-danger"
|
||||
*ngIf="!activeOrganization.enabled"
|
||||
>
|
||||
<div class="tw-py-2 tw-px-5">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "organizationIsDisabled" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tw-ml-3 tw-border tw-border-solid tw-rounded tw-border-info-500 tw-text-info"
|
||||
*ngIf="activeOrganization.isProviderUser"
|
||||
>
|
||||
<div class="tw-py-2 tw-px-5">
|
||||
<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-p-0 tw-m-0">
|
||||
<li *ngFor="let org of organizations" class="tw-list-none tw-flex tw-flex-col" role="none">
|
||||
<a bit-menu-item [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 bit-menu-item routerLink="/settings/create-organization">
|
||||
<i class="bwi bwi-plus mr-2"></i>
|
||||
{{ "newOrganization" | i18n }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</bit-menu>
|
||||
</div>
|
34
src/app/components/organization-switcher.component.ts
Normal file
34
src/app/components/organization-switcher.component.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
|
||||
import { NavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
|
||||
|
||||
@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: Organization[] = [];
|
||||
|
||||
loaded = false;
|
||||
|
||||
async ngOnInit() {
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async load() {
|
||||
const orgs = await this.organizationService.getAll();
|
||||
this.organizations = orgs
|
||||
.filter((org) => NavigationPermissionsService.canAccessAdmin(org))
|
||||
.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
|
||||
this.loaded = true;
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/vault">{{ "myVault" | i18n }}</a>
|
||||
<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>
|
||||
@ -27,70 +27,67 @@
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/settings">{{ "settings" | i18n }}</a>
|
||||
<li *ngIf="organizations.length >= 1" class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" [routerLink]="['/organizations', organizations[0].id]">{{
|
||||
"organizations" | i18n
|
||||
}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li class="nav-item dropdown">
|
||||
<a
|
||||
class="nav-item nav-link dropdown-toggle"
|
||||
href="#"
|
||||
id="nav-profile"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
<li>
|
||||
<button
|
||||
[bitMenuTriggerFor]="accountMenu"
|
||||
class="tw-border-0 tw-bg-transparent tw-text-contrast tw-opacity-70 hover:tw-opacity-90"
|
||||
>
|
||||
<i class="bwi bwi-user-circle bwi-lg" aria-hidden="true"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-profile">
|
||||
<div class="dropdown-item-text d-flex align-items-center" *ngIf="name" appStopProp>
|
||||
<app-avatar
|
||||
[data]="name"
|
||||
[email]="email"
|
||||
size="25"
|
||||
fontSize="14"
|
||||
[circle]="true"
|
||||
></app-avatar>
|
||||
<div class="ml-2 overflow-hidden">
|
||||
<span>{{ "loggedInAs" | i18n }}</span>
|
||||
<small class="text-muted">{{ name }}</small>
|
||||
<i class="bwi bwi-caret-down bwi-sm" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu class="dropdown-menu" #accountMenu>
|
||||
<div class="tw-max-w-[300px] tw-min-w-[200px] tw-flex tw-flex-col">
|
||||
<div
|
||||
class="tw-flex tw-items-center tw-leading-tight tw-text-info tw-py-1 tw-px-4"
|
||||
*ngIf="name"
|
||||
appStopProp
|
||||
>
|
||||
<app-avatar
|
||||
[data]="name"
|
||||
[email]="email"
|
||||
size="25"
|
||||
fontSize="14"
|
||||
[circle]="true"
|
||||
></app-avatar>
|
||||
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
|
||||
<span>{{ "loggedInAs" | i18n }}</span>
|
||||
<small class="tw-text-muted tw-block tw-overflow-hidden tw-whitespace-nowrap">{{
|
||||
name
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
<a bit-menu-item routerLink="/settings/account">
|
||||
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
|
||||
{{ "accountSettings" | i18n }}
|
||||
</a>
|
||||
<a bit-menu-item href="https://bitwarden.com/help/" target="_blank" rel="noopener">
|
||||
<i class="bwi bwi-fw bwi-question-circle" aria-hidden="true"></i>
|
||||
{{ "getHelp" | i18n }}
|
||||
</a>
|
||||
<a bit-menu-item href="https://bitwarden.com/download/" target="_blank" rel="noopener">
|
||||
<i class="bwi bwi-fw bwi-download" aria-hidden="true"></i>
|
||||
{{ "getApps" | i18n }}
|
||||
</a>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
<button bit-menu-item type="button" (click)="lock()">
|
||||
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
|
||||
{{ "lockNow" | i18n }}
|
||||
</button>
|
||||
<button bit-menu-item type="button" (click)="logOut()">
|
||||
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
|
||||
{{ "logOut" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#" routerLink="/settings/account">
|
||||
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
|
||||
{{ "myAccount" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="https://bitwarden.com/help/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-question-circle" aria-hidden="true"></i>
|
||||
{{ "getHelp" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="https://bitwarden.com/download/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-download" aria-hidden="true"></i>
|
||||
{{ "getApps" | i18n }}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button type="button" class="dropdown-item" (click)="lock()">
|
||||
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
|
||||
{{ "lockNow" | i18n }}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item" (click)="logOut()">
|
||||
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
|
||||
{{ "logOut" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</bit-menu>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -1,12 +1,18 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
import { Provider } from "jslib-common/models/domain/provider";
|
||||
|
||||
import { NavigationPermissionsService as OrgNavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-navbar",
|
||||
templateUrl: "navbar.component.html",
|
||||
@ -16,13 +22,16 @@ export class NavbarComponent implements OnInit {
|
||||
name: string;
|
||||
email: string;
|
||||
providers: Provider[] = [];
|
||||
organizations: Organization[] = [];
|
||||
|
||||
constructor(
|
||||
private messagingService: MessagingService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private tokenService: TokenService,
|
||||
private providerService: ProviderService,
|
||||
private syncService: SyncService
|
||||
private syncService: SyncService,
|
||||
private organizationService: OrganizationService,
|
||||
private i18nService: I18nService
|
||||
) {
|
||||
this.selfHosted = this.platformUtilsService.isSelfHost();
|
||||
}
|
||||
@ -34,11 +43,16 @@ export class NavbarComponent implements OnInit {
|
||||
this.name = this.email;
|
||||
}
|
||||
|
||||
// Ensure provides are loaded
|
||||
// Ensure providers and organizations are loaded
|
||||
if ((await this.syncService.getLastSync()) == null) {
|
||||
await this.syncService.fullSync(false);
|
||||
}
|
||||
this.providers = await this.providerService.getAll();
|
||||
|
||||
const allOrgs = await this.organizationService.getAll();
|
||||
this.organizations = allOrgs
|
||||
.filter((org) => OrgNavigationPermissionsService.canAccessAdmin(org))
|
||||
.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
}
|
||||
|
||||
lock() {
|
||||
|
@ -1,60 +0,0 @@
|
||||
<app-navbar></app-navbar>
|
||||
<div class="org-nav" *ngIf="organization">
|
||||
<div class="container d-flex">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="my-auto d-flex align-items-center pl-1">
|
||||
<app-avatar [data]="organization.name" size="45" [circle]="true"></app-avatar>
|
||||
<div class="org-name ml-3">
|
||||
<span>{{ organization.name }}</span>
|
||||
<small class="text-muted">{{ "organization" | i18n }}</small>
|
||||
</div>
|
||||
<div
|
||||
class="ml-3 card border-danger text-danger bg-transparent"
|
||||
*ngIf="!organization.enabled"
|
||||
>
|
||||
<div class="card-body py-2">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "organizationIsDisabled" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ml-3 card border-info text-info bg-transparent"
|
||||
*ngIf="organization.isProviderUser"
|
||||
>
|
||||
<div class="card-body py-2">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "accessingUsingProvider" | i18n: organization.providerName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-tabs" *ngIf="showMenuBar">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="vault" routerLinkActive="active">
|
||||
<i class="bwi bwi-lock" aria-hidden="true"></i>
|
||||
{{ "vault" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="showManageTab">
|
||||
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
|
||||
<i class="bwi bwi-sliders" aria-hidden="true"></i>
|
||||
{{ "manage" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="showToolsTab">
|
||||
<a class="nav-link" [routerLink]="toolsRoute" routerLinkActive="active">
|
||||
<i class="bwi bwi-wrench" aria-hidden="true"></i>
|
||||
{{ "tools" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="organization.isOwner">
|
||||
<a class="nav-link" routerLink="settings" routerLinkActive="active">
|
||||
<i class="bwi bwi-cogs" aria-hidden="true"></i>
|
||||
{{ "settings" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
<app-footer></app-footer>
|
491
src/app/modules/loose-components.module.ts
Normal file
491
src/app/modules/loose-components.module.ts
Normal file
@ -0,0 +1,491 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { UserVerificationComponent } from "jslib-angular/components/user-verification.component";
|
||||
|
||||
import { AcceptEmergencyComponent } from "../accounts/accept-emergency.component";
|
||||
import { AcceptOrganizationComponent } from "../accounts/accept-organization.component";
|
||||
import { HintComponent } from "../accounts/hint.component";
|
||||
import { LockComponent } from "../accounts/lock.component";
|
||||
import { LoginComponent } from "../accounts/login.component";
|
||||
import { RecoverDeleteComponent } from "../accounts/recover-delete.component";
|
||||
import { RecoverTwoFactorComponent } from "../accounts/recover-two-factor.component";
|
||||
import { RegisterComponent } from "../accounts/register.component";
|
||||
import { RemovePasswordComponent } from "../accounts/remove-password.component";
|
||||
import { SetPasswordComponent } from "../accounts/set-password.component";
|
||||
import { SsoComponent } from "../accounts/sso.component";
|
||||
import { TwoFactorOptionsComponent } from "../accounts/two-factor-options.component";
|
||||
import { TwoFactorComponent } from "../accounts/two-factor.component";
|
||||
import { UpdatePasswordComponent } from "../accounts/update-password.component";
|
||||
import { UpdateTempPasswordComponent } from "../accounts/update-temp-password.component";
|
||||
import { VerifyEmailTokenComponent } from "../accounts/verify-email-token.component";
|
||||
import { VerifyRecoverDeleteComponent } from "../accounts/verify-recover-delete.component";
|
||||
import { NestedCheckboxComponent } from "../components/nested-checkbox.component";
|
||||
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
|
||||
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
|
||||
import { PasswordStrengthComponent } from "../components/password-strength.component";
|
||||
import { PremiumBadgeComponent } from "../components/premium-badge.component";
|
||||
import { FooterComponent } from "../layouts/footer.component";
|
||||
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
|
||||
import { NavbarComponent } from "../layouts/navbar.component";
|
||||
import { UserLayoutComponent } from "../layouts/user-layout.component";
|
||||
import { OrganizationLayoutComponent } from "../organizations/layouts/organization-layout.component";
|
||||
import { BulkConfirmComponent as OrgBulkConfirmComponent } from "../organizations/manage/bulk/bulk-confirm.component";
|
||||
import { BulkRemoveComponent as OrgBulkRemoveComponent } from "../organizations/manage/bulk/bulk-remove.component";
|
||||
import { BulkStatusComponent as OrgBulkStatusComponent } from "../organizations/manage/bulk/bulk-status.component";
|
||||
import { CollectionAddEditComponent as OrgCollectionAddEditComponent } from "../organizations/manage/collection-add-edit.component";
|
||||
import { CollectionsComponent as OrgManageCollectionsComponent } from "../organizations/manage/collections.component";
|
||||
import { EntityEventsComponent as OrgEntityEventsComponent } from "../organizations/manage/entity-events.component";
|
||||
import { EntityUsersComponent as OrgEntityUsersComponent } from "../organizations/manage/entity-users.component";
|
||||
import { EventsComponent as OrgEventsComponent } from "../organizations/manage/events.component";
|
||||
import { GroupAddEditComponent as OrgGroupAddEditComponent } from "../organizations/manage/group-add-edit.component";
|
||||
import { GroupsComponent as OrgGroupsComponent } from "../organizations/manage/groups.component";
|
||||
import { ManageComponent as OrgManageComponent } from "../organizations/manage/manage.component";
|
||||
import { PeopleComponent as OrgPeopleComponent } from "../organizations/manage/people.component";
|
||||
import { PoliciesComponent as OrgPoliciesComponent } from "../organizations/manage/policies.component";
|
||||
import { PolicyEditComponent as OrgPolicyEditComponent } from "../organizations/manage/policy-edit.component";
|
||||
import { ResetPasswordComponent as OrgResetPasswordComponent } from "../organizations/manage/reset-password.component";
|
||||
import { UserAddEditComponent as OrgUserAddEditComponent } from "../organizations/manage/user-add-edit.component";
|
||||
import { UserConfirmComponent as OrgUserConfirmComponent } from "../organizations/manage/user-confirm.component";
|
||||
import { UserGroupsComponent as OrgUserGroupsComponent } from "../organizations/manage/user-groups.component";
|
||||
import { DisableSendPolicyComponent } from "../organizations/policies/disable-send.component";
|
||||
import { MasterPasswordPolicyComponent } from "../organizations/policies/master-password.component";
|
||||
import { PasswordGeneratorPolicyComponent } from "../organizations/policies/password-generator.component";
|
||||
import { PersonalOwnershipPolicyComponent } from "../organizations/policies/personal-ownership.component";
|
||||
import { RequireSsoPolicyComponent } from "../organizations/policies/require-sso.component";
|
||||
import { ResetPasswordPolicyComponent } from "../organizations/policies/reset-password.component";
|
||||
import { SendOptionsPolicyComponent } from "../organizations/policies/send-options.component";
|
||||
import { SingleOrgPolicyComponent } from "../organizations/policies/single-org.component";
|
||||
import { TwoFactorAuthenticationPolicyComponent } from "../organizations/policies/two-factor-authentication.component";
|
||||
import { AccountComponent as OrgAccountComponent } from "../organizations/settings/account.component";
|
||||
import { AdjustSubscription } from "../organizations/settings/adjust-subscription.component";
|
||||
import { ChangePlanComponent } from "../organizations/settings/change-plan.component";
|
||||
import { DeleteOrganizationComponent } from "../organizations/settings/delete-organization.component";
|
||||
import { DownloadLicenseComponent } from "../organizations/settings/download-license.component";
|
||||
import { OrganizationBillingComponent } from "../organizations/settings/organization-billing.component";
|
||||
import { OrganizationSubscriptionComponent } from "../organizations/settings/organization-subscription.component";
|
||||
import { SettingsComponent as OrgSettingComponent } from "../organizations/settings/settings.component";
|
||||
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "../organizations/settings/two-factor-setup.component";
|
||||
import { FamiliesForEnterpriseSetupComponent } from "../organizations/sponsorships/families-for-enterprise-setup.component";
|
||||
import { ExportComponent as OrgExportComponent } from "../organizations/tools/export.component";
|
||||
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../organizations/tools/exposed-passwords-report.component";
|
||||
import { ImportComponent as OrgImportComponent } from "../organizations/tools/import.component";
|
||||
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../organizations/tools/inactive-two-factor-report.component";
|
||||
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../organizations/tools/reused-passwords-report.component";
|
||||
import { ToolsComponent as OrgToolsComponent } from "../organizations/tools/tools.component";
|
||||
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../organizations/tools/unsecured-websites-report.component";
|
||||
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../organizations/tools/weak-passwords-report.component";
|
||||
import { AddEditComponent as OrgAddEditComponent } from "../organizations/vault/add-edit.component";
|
||||
import { AttachmentsComponent as OrgAttachmentsComponent } from "../organizations/vault/attachments.component";
|
||||
import { CiphersComponent as OrgCiphersComponent } from "../organizations/vault/ciphers.component";
|
||||
import { CollectionsComponent as OrgCollectionsComponent } from "../organizations/vault/collections.component";
|
||||
import { ProvidersComponent } from "../providers/providers.component";
|
||||
import { BreachReportComponent } from "../reports/breach-report.component";
|
||||
import { ExposedPasswordsReportComponent } from "../reports/exposed-passwords-report.component";
|
||||
import { InactiveTwoFactorReportComponent } from "../reports/inactive-two-factor-report.component";
|
||||
import { ReportCardComponent } from "../reports/report-card.component";
|
||||
import { ReportListComponent } from "../reports/report-list.component";
|
||||
import { ReportsComponent } from "../reports/reports.component";
|
||||
import { ReusedPasswordsReportComponent } from "../reports/reused-passwords-report.component";
|
||||
import { UnsecuredWebsitesReportComponent } from "../reports/unsecured-websites-report.component";
|
||||
import { WeakPasswordsReportComponent } from "../reports/weak-passwords-report.component";
|
||||
import { AccessComponent } from "../send/access.component";
|
||||
import { AddEditComponent as SendAddEditComponent } from "../send/add-edit.component";
|
||||
import { EffluxDatesComponent as SendEffluxDatesComponent } from "../send/efflux-dates.component";
|
||||
import { SendComponent } from "../send/send.component";
|
||||
import { AccountComponent } from "../settings/account.component";
|
||||
import { AddCreditComponent } from "../settings/add-credit.component";
|
||||
import { AdjustPaymentComponent } from "../settings/adjust-payment.component";
|
||||
import { AdjustStorageComponent } from "../settings/adjust-storage.component";
|
||||
import { ApiKeyComponent } from "../settings/api-key.component";
|
||||
import { ChangeEmailComponent } from "../settings/change-email.component";
|
||||
import { ChangeKdfComponent } from "../settings/change-kdf.component";
|
||||
import { ChangePasswordComponent } from "../settings/change-password.component";
|
||||
import { CreateOrganizationComponent } from "../settings/create-organization.component";
|
||||
import { DeauthorizeSessionsComponent } from "../settings/deauthorize-sessions.component";
|
||||
import { DeleteAccountComponent } from "../settings/delete-account.component";
|
||||
import { DomainRulesComponent } from "../settings/domain-rules.component";
|
||||
import { EmergencyAccessAddEditComponent } from "../settings/emergency-access-add-edit.component";
|
||||
import { EmergencyAccessAttachmentsComponent } from "../settings/emergency-access-attachments.component";
|
||||
import { EmergencyAccessConfirmComponent } from "../settings/emergency-access-confirm.component";
|
||||
import { EmergencyAccessTakeoverComponent } from "../settings/emergency-access-takeover.component";
|
||||
import { EmergencyAccessViewComponent } from "../settings/emergency-access-view.component";
|
||||
import { EmergencyAccessComponent } from "../settings/emergency-access.component";
|
||||
import { EmergencyAddEditComponent } from "../settings/emergency-add-edit.component";
|
||||
import { LinkSsoComponent } from "../settings/link-sso.component";
|
||||
import { OrganizationPlansComponent } from "../settings/organization-plans.component";
|
||||
import { PaymentMethodComponent } from "../settings/payment-method.component";
|
||||
import { PaymentComponent } from "../settings/payment.component";
|
||||
import { PreferencesComponent } from "../settings/preferences.component";
|
||||
import { PremiumComponent } from "../settings/premium.component";
|
||||
import { ProfileComponent } from "../settings/profile.component";
|
||||
import { PurgeVaultComponent } from "../settings/purge-vault.component";
|
||||
import { SecurityKeysComponent } from "../settings/security-keys.component";
|
||||
import { SecurityComponent } from "../settings/security.component";
|
||||
import { SettingsComponent } from "../settings/settings.component";
|
||||
import { SponsoredFamiliesComponent } from "../settings/sponsored-families.component";
|
||||
import { SponsoringOrgRowComponent } from "../settings/sponsoring-org-row.component";
|
||||
import { SubscriptionComponent } from "../settings/subscription.component";
|
||||
import { TaxInfoComponent } from "../settings/tax-info.component";
|
||||
import { TwoFactorAuthenticatorComponent } from "../settings/two-factor-authenticator.component";
|
||||
import { TwoFactorDuoComponent } from "../settings/two-factor-duo.component";
|
||||
import { TwoFactorEmailComponent } from "../settings/two-factor-email.component";
|
||||
import { TwoFactorRecoveryComponent } from "../settings/two-factor-recovery.component";
|
||||
import { TwoFactorSetupComponent } from "../settings/two-factor-setup.component";
|
||||
import { TwoFactorVerifyComponent } from "../settings/two-factor-verify.component";
|
||||
import { TwoFactorWebAuthnComponent } from "../settings/two-factor-webauthn.component";
|
||||
import { TwoFactorYubiKeyComponent } from "../settings/two-factor-yubikey.component";
|
||||
import { UpdateKeyComponent } from "../settings/update-key.component";
|
||||
import { UpdateLicenseComponent } from "../settings/update-license.component";
|
||||
import { UserBillingHistoryComponent } from "../settings/user-billing-history.component";
|
||||
import { UserSubscriptionComponent } from "../settings/user-subscription.component";
|
||||
import { VaultTimeoutInputComponent } from "../settings/vault-timeout-input.component";
|
||||
import { VerifyEmailComponent } from "../settings/verify-email.component";
|
||||
import { ExportComponent } from "../tools/export.component";
|
||||
import { GeneratorComponent } from "../tools/generator.component";
|
||||
import { ImportComponent } from "../tools/import.component";
|
||||
import { PasswordGeneratorHistoryComponent } from "../tools/password-generator-history.component";
|
||||
import { ToolsComponent } from "../tools/tools.component";
|
||||
import { AddEditCustomFieldsComponent } from "../vault/add-edit-custom-fields.component";
|
||||
import { AddEditComponent } from "../vault/add-edit.component";
|
||||
import { AttachmentsComponent } from "../vault/attachments.component";
|
||||
import { BulkActionsComponent } from "../vault/bulk-actions.component";
|
||||
import { BulkDeleteComponent } from "../vault/bulk-delete.component";
|
||||
import { BulkMoveComponent } from "../vault/bulk-move.component";
|
||||
import { BulkRestoreComponent } from "../vault/bulk-restore.component";
|
||||
import { BulkShareComponent } from "../vault/bulk-share.component";
|
||||
import { CiphersComponent } from "../vault/ciphers.component";
|
||||
import { CollectionsComponent } from "../vault/collections.component";
|
||||
import { FolderAddEditComponent } from "../vault/folder-add-edit.component";
|
||||
import { ShareComponent } from "../vault/share.component";
|
||||
|
||||
import { PipesModule } from "./pipes/pipes.module";
|
||||
import { SharedModule } from "./shared.module";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
import { OrganizationBadgeModule } from "./vault/modules/organization-badge/organization-badge.module";
|
||||
|
||||
// Please do not add to this list of declarations - we should refactor these into modules when doing so makes sense until there are none left.
|
||||
// If you are building new functionality, please create or extend a feature module instead.
|
||||
@NgModule({
|
||||
imports: [SharedModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
|
||||
declarations: [
|
||||
PremiumBadgeComponent,
|
||||
AcceptEmergencyComponent,
|
||||
AcceptOrganizationComponent,
|
||||
AccessComponent,
|
||||
AccountComponent,
|
||||
AddCreditComponent,
|
||||
AddEditComponent,
|
||||
AddEditCustomFieldsComponent,
|
||||
AddEditCustomFieldsComponent,
|
||||
AdjustPaymentComponent,
|
||||
AdjustStorageComponent,
|
||||
AdjustSubscription,
|
||||
ApiKeyComponent,
|
||||
AttachmentsComponent,
|
||||
BreachReportComponent,
|
||||
BulkActionsComponent,
|
||||
BulkDeleteComponent,
|
||||
BulkMoveComponent,
|
||||
BulkRestoreComponent,
|
||||
BulkShareComponent,
|
||||
ChangeEmailComponent,
|
||||
ChangeKdfComponent,
|
||||
ChangePasswordComponent,
|
||||
ChangePlanComponent,
|
||||
CiphersComponent,
|
||||
CollectionsComponent,
|
||||
CreateOrganizationComponent,
|
||||
DeauthorizeSessionsComponent,
|
||||
DeleteAccountComponent,
|
||||
DeleteOrganizationComponent,
|
||||
DisableSendPolicyComponent,
|
||||
DomainRulesComponent,
|
||||
DownloadLicenseComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessComponent,
|
||||
EmergencyAccessConfirmComponent,
|
||||
EmergencyAccessTakeoverComponent,
|
||||
EmergencyAccessViewComponent,
|
||||
EmergencyAddEditComponent,
|
||||
ExportComponent,
|
||||
ExposedPasswordsReportComponent,
|
||||
FamiliesForEnterpriseSetupComponent,
|
||||
FolderAddEditComponent,
|
||||
FooterComponent,
|
||||
FrontendLayoutComponent,
|
||||
HintComponent,
|
||||
ImportComponent,
|
||||
InactiveTwoFactorReportComponent,
|
||||
LinkSsoComponent,
|
||||
LockComponent,
|
||||
LoginComponent,
|
||||
MasterPasswordPolicyComponent,
|
||||
NavbarComponent,
|
||||
NestedCheckboxComponent,
|
||||
OrganizationSwitcherComponent,
|
||||
OrgAccountComponent,
|
||||
OrgAddEditComponent,
|
||||
OrganizationBillingComponent,
|
||||
OrganizationLayoutComponent,
|
||||
OrganizationPlansComponent,
|
||||
OrganizationSubscriptionComponent,
|
||||
OrgAttachmentsComponent,
|
||||
OrgBulkConfirmComponent,
|
||||
OrgBulkRemoveComponent,
|
||||
OrgBulkStatusComponent,
|
||||
OrgCiphersComponent,
|
||||
OrgCollectionAddEditComponent,
|
||||
OrgCollectionsComponent,
|
||||
OrgEntityEventsComponent,
|
||||
OrgEntityUsersComponent,
|
||||
OrgEventsComponent,
|
||||
OrgExportComponent,
|
||||
OrgExposedPasswordsReportComponent,
|
||||
OrgGroupAddEditComponent,
|
||||
OrgGroupsComponent,
|
||||
OrgImportComponent,
|
||||
OrgInactiveTwoFactorReportComponent,
|
||||
OrgManageCollectionsComponent,
|
||||
OrgManageComponent,
|
||||
OrgPeopleComponent,
|
||||
OrgPoliciesComponent,
|
||||
OrgPolicyEditComponent,
|
||||
OrgResetPasswordComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgSettingComponent,
|
||||
OrgToolsComponent,
|
||||
OrgTwoFactorSetupComponent,
|
||||
OrgUnsecuredWebsitesReportComponent,
|
||||
OrgUserAddEditComponent,
|
||||
OrgUserConfirmComponent,
|
||||
OrgUserGroupsComponent,
|
||||
OrgWeakPasswordsReportComponent,
|
||||
GeneratorComponent,
|
||||
PasswordGeneratorHistoryComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
PasswordRepromptComponent,
|
||||
PasswordStrengthComponent,
|
||||
PaymentComponent,
|
||||
PaymentMethodComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
PreferencesComponent,
|
||||
PremiumBadgeComponent,
|
||||
PremiumComponent,
|
||||
ProfileComponent,
|
||||
ProvidersComponent,
|
||||
PurgeVaultComponent,
|
||||
RecoverDeleteComponent,
|
||||
RecoverTwoFactorComponent,
|
||||
RegisterComponent,
|
||||
RemovePasswordComponent,
|
||||
ReportCardComponent,
|
||||
ReportListComponent,
|
||||
ReportsComponent,
|
||||
RequireSsoPolicyComponent,
|
||||
ResetPasswordPolicyComponent,
|
||||
ReusedPasswordsReportComponent,
|
||||
SecurityComponent,
|
||||
SecurityKeysComponent,
|
||||
SendAddEditComponent,
|
||||
SendComponent,
|
||||
SendEffluxDatesComponent,
|
||||
SendOptionsPolicyComponent,
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
SingleOrgPolicyComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponent,
|
||||
SubscriptionComponent,
|
||||
TaxInfoComponent,
|
||||
ToolsComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
TwoFactorAuthenticatorComponent,
|
||||
TwoFactorComponent,
|
||||
TwoFactorDuoComponent,
|
||||
TwoFactorEmailComponent,
|
||||
TwoFactorOptionsComponent,
|
||||
TwoFactorRecoveryComponent,
|
||||
TwoFactorSetupComponent,
|
||||
TwoFactorVerifyComponent,
|
||||
TwoFactorWebAuthnComponent,
|
||||
TwoFactorYubiKeyComponent,
|
||||
UnsecuredWebsitesReportComponent,
|
||||
UpdateKeyComponent,
|
||||
UpdateLicenseComponent,
|
||||
UpdatePasswordComponent,
|
||||
UpdateTempPasswordComponent,
|
||||
UserBillingHistoryComponent,
|
||||
UserLayoutComponent,
|
||||
UserSubscriptionComponent,
|
||||
UserVerificationComponent,
|
||||
VaultTimeoutInputComponent,
|
||||
VerifyEmailComponent,
|
||||
VerifyEmailTokenComponent,
|
||||
VerifyRecoverDeleteComponent,
|
||||
WeakPasswordsReportComponent,
|
||||
],
|
||||
exports: [
|
||||
PremiumBadgeComponent,
|
||||
AcceptEmergencyComponent,
|
||||
AcceptOrganizationComponent,
|
||||
AccessComponent,
|
||||
AccountComponent,
|
||||
AddCreditComponent,
|
||||
AddEditComponent,
|
||||
AddEditCustomFieldsComponent,
|
||||
AddEditCustomFieldsComponent,
|
||||
AdjustPaymentComponent,
|
||||
AdjustStorageComponent,
|
||||
AdjustSubscription,
|
||||
ApiKeyComponent,
|
||||
AttachmentsComponent,
|
||||
BreachReportComponent,
|
||||
BulkActionsComponent,
|
||||
BulkDeleteComponent,
|
||||
BulkMoveComponent,
|
||||
BulkRestoreComponent,
|
||||
BulkShareComponent,
|
||||
ChangeEmailComponent,
|
||||
ChangeKdfComponent,
|
||||
ChangePasswordComponent,
|
||||
ChangePlanComponent,
|
||||
CiphersComponent,
|
||||
CollectionsComponent,
|
||||
CreateOrganizationComponent,
|
||||
DeauthorizeSessionsComponent,
|
||||
DeleteAccountComponent,
|
||||
DeleteOrganizationComponent,
|
||||
DisableSendPolicyComponent,
|
||||
DomainRulesComponent,
|
||||
DownloadLicenseComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessComponent,
|
||||
EmergencyAccessConfirmComponent,
|
||||
EmergencyAccessTakeoverComponent,
|
||||
EmergencyAccessViewComponent,
|
||||
EmergencyAddEditComponent,
|
||||
ExportComponent,
|
||||
ExposedPasswordsReportComponent,
|
||||
FamiliesForEnterpriseSetupComponent,
|
||||
FolderAddEditComponent,
|
||||
FooterComponent,
|
||||
FrontendLayoutComponent,
|
||||
HintComponent,
|
||||
ImportComponent,
|
||||
InactiveTwoFactorReportComponent,
|
||||
LinkSsoComponent,
|
||||
LockComponent,
|
||||
LoginComponent,
|
||||
MasterPasswordPolicyComponent,
|
||||
NavbarComponent,
|
||||
NestedCheckboxComponent,
|
||||
OrganizationSwitcherComponent,
|
||||
OrgAccountComponent,
|
||||
OrgAddEditComponent,
|
||||
OrganizationBillingComponent,
|
||||
OrganizationLayoutComponent,
|
||||
OrganizationPlansComponent,
|
||||
OrganizationSubscriptionComponent,
|
||||
OrgAttachmentsComponent,
|
||||
OrgBulkConfirmComponent,
|
||||
OrgBulkRemoveComponent,
|
||||
OrgBulkStatusComponent,
|
||||
OrgCiphersComponent,
|
||||
OrgCollectionAddEditComponent,
|
||||
OrgCollectionsComponent,
|
||||
OrgEntityEventsComponent,
|
||||
OrgEntityUsersComponent,
|
||||
OrgEventsComponent,
|
||||
OrgExportComponent,
|
||||
OrgExposedPasswordsReportComponent,
|
||||
OrgGroupAddEditComponent,
|
||||
OrgGroupsComponent,
|
||||
OrgImportComponent,
|
||||
OrgInactiveTwoFactorReportComponent,
|
||||
OrgManageCollectionsComponent,
|
||||
OrgManageComponent,
|
||||
OrgPeopleComponent,
|
||||
OrgPoliciesComponent,
|
||||
OrgPolicyEditComponent,
|
||||
OrgResetPasswordComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgSettingComponent,
|
||||
OrgToolsComponent,
|
||||
OrgTwoFactorSetupComponent,
|
||||
OrgUnsecuredWebsitesReportComponent,
|
||||
OrgUserAddEditComponent,
|
||||
OrgUserConfirmComponent,
|
||||
OrgUserGroupsComponent,
|
||||
OrgWeakPasswordsReportComponent,
|
||||
GeneratorComponent,
|
||||
PasswordGeneratorHistoryComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
PasswordRepromptComponent,
|
||||
PasswordStrengthComponent,
|
||||
PaymentComponent,
|
||||
PaymentMethodComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
PreferencesComponent,
|
||||
PremiumBadgeComponent,
|
||||
PremiumComponent,
|
||||
ProfileComponent,
|
||||
ProvidersComponent,
|
||||
PurgeVaultComponent,
|
||||
RecoverDeleteComponent,
|
||||
RecoverTwoFactorComponent,
|
||||
RegisterComponent,
|
||||
RemovePasswordComponent,
|
||||
ReportCardComponent,
|
||||
ReportListComponent,
|
||||
ReportsComponent,
|
||||
RequireSsoPolicyComponent,
|
||||
ResetPasswordPolicyComponent,
|
||||
ReusedPasswordsReportComponent,
|
||||
SecurityComponent,
|
||||
SecurityKeysComponent,
|
||||
SendAddEditComponent,
|
||||
SendComponent,
|
||||
SendEffluxDatesComponent,
|
||||
SendOptionsPolicyComponent,
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
SingleOrgPolicyComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponent,
|
||||
SubscriptionComponent,
|
||||
TaxInfoComponent,
|
||||
ToolsComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
TwoFactorAuthenticatorComponent,
|
||||
TwoFactorComponent,
|
||||
TwoFactorDuoComponent,
|
||||
TwoFactorEmailComponent,
|
||||
TwoFactorOptionsComponent,
|
||||
TwoFactorRecoveryComponent,
|
||||
TwoFactorSetupComponent,
|
||||
TwoFactorVerifyComponent,
|
||||
TwoFactorWebAuthnComponent,
|
||||
TwoFactorYubiKeyComponent,
|
||||
UnsecuredWebsitesReportComponent,
|
||||
UpdateKeyComponent,
|
||||
UpdateLicenseComponent,
|
||||
UpdatePasswordComponent,
|
||||
UpdateTempPasswordComponent,
|
||||
UserBillingHistoryComponent,
|
||||
UserLayoutComponent,
|
||||
UserSubscriptionComponent,
|
||||
UserVerificationComponent,
|
||||
VaultTimeoutInputComponent,
|
||||
VerifyEmailComponent,
|
||||
VerifyEmailTokenComponent,
|
||||
VerifyRecoverDeleteComponent,
|
||||
WeakPasswordsReportComponent,
|
||||
],
|
||||
})
|
||||
export class LooseComponentsModule {}
|
14
src/app/modules/pipes/get-organization-name.pipe.ts
Normal file
14
src/app/modules/pipes/get-organization-name.pipe.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
|
||||
@Pipe({
|
||||
name: "orgNameFromId",
|
||||
pure: true,
|
||||
})
|
||||
export class GetOrgNameFromIdPipe implements PipeTransform {
|
||||
transform(value: string, organizations: Organization[]) {
|
||||
const orgName = organizations.find((o) => o.id === value)?.name;
|
||||
return orgName;
|
||||
}
|
||||
}
|
10
src/app/modules/pipes/pipes.module.ts
Normal file
10
src/app/modules/pipes/pipes.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { GetOrgNameFromIdPipe } from "./get-organization-name.pipe";
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
declarations: [GetOrgNameFromIdPipe],
|
||||
exports: [GetOrgNameFromIdPipe],
|
||||
})
|
||||
export class PipesModule {}
|
149
src/app/modules/shared.module.ts
Normal file
149
src/app/modules/shared.module.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import { DragDropModule } from "@angular/cdk/drag-drop";
|
||||
import { DatePipe, registerLocaleData, CommonModule } from "@angular/common";
|
||||
import localeAf from "@angular/common/locales/af";
|
||||
import localeAz from "@angular/common/locales/az";
|
||||
import localeBe from "@angular/common/locales/be";
|
||||
import localeBg from "@angular/common/locales/bg";
|
||||
import localeBn from "@angular/common/locales/bn";
|
||||
import localeBs from "@angular/common/locales/bs";
|
||||
import localeCa from "@angular/common/locales/ca";
|
||||
import localeCs from "@angular/common/locales/cs";
|
||||
import localeDa from "@angular/common/locales/da";
|
||||
import localeDe from "@angular/common/locales/de";
|
||||
import localeEl from "@angular/common/locales/el";
|
||||
import localeEnGb from "@angular/common/locales/en-GB";
|
||||
import localeEnIn from "@angular/common/locales/en-IN";
|
||||
import localeEo from "@angular/common/locales/eo";
|
||||
import localeEs from "@angular/common/locales/es";
|
||||
import localeEt from "@angular/common/locales/et";
|
||||
import localeFi from "@angular/common/locales/fi";
|
||||
import localeFil from "@angular/common/locales/fil";
|
||||
import localeFr from "@angular/common/locales/fr";
|
||||
import localeHe from "@angular/common/locales/he";
|
||||
import localeHi from "@angular/common/locales/hi";
|
||||
import localeHr from "@angular/common/locales/hr";
|
||||
import localeHu from "@angular/common/locales/hu";
|
||||
import localeId from "@angular/common/locales/id";
|
||||
import localeIt from "@angular/common/locales/it";
|
||||
import localeJa from "@angular/common/locales/ja";
|
||||
import localeKa from "@angular/common/locales/ka";
|
||||
import localeKm from "@angular/common/locales/km";
|
||||
import localeKn from "@angular/common/locales/kn";
|
||||
import localeKo from "@angular/common/locales/ko";
|
||||
import localeLv from "@angular/common/locales/lv";
|
||||
import localeMl from "@angular/common/locales/ml";
|
||||
import localeNb from "@angular/common/locales/nb";
|
||||
import localeNl from "@angular/common/locales/nl";
|
||||
import localeNn from "@angular/common/locales/nn";
|
||||
import localePl from "@angular/common/locales/pl";
|
||||
import localePtBr from "@angular/common/locales/pt";
|
||||
import localePtPt from "@angular/common/locales/pt-PT";
|
||||
import localeRo from "@angular/common/locales/ro";
|
||||
import localeRu from "@angular/common/locales/ru";
|
||||
import localeSi from "@angular/common/locales/si";
|
||||
import localeSk from "@angular/common/locales/sk";
|
||||
import localeSl from "@angular/common/locales/sl";
|
||||
import localeSr from "@angular/common/locales/sr";
|
||||
import localeSv from "@angular/common/locales/sv";
|
||||
import localeTr from "@angular/common/locales/tr";
|
||||
import localeUk from "@angular/common/locales/uk";
|
||||
import localeVi from "@angular/common/locales/vi";
|
||||
import localeZhCn from "@angular/common/locales/zh-Hans";
|
||||
import localeZhTw from "@angular/common/locales/zh-Hant";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { BadgeModule, ButtonModule, CalloutModule, MenuModule } from "@bitwarden/components";
|
||||
import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
||||
import { ToastrModule } from "ngx-toastr";
|
||||
|
||||
import { JslibModule } from "jslib-angular/jslib.module";
|
||||
|
||||
registerLocaleData(localeAf, "af");
|
||||
registerLocaleData(localeAz, "az");
|
||||
registerLocaleData(localeBe, "be");
|
||||
registerLocaleData(localeBg, "bg");
|
||||
registerLocaleData(localeBn, "bn");
|
||||
registerLocaleData(localeBs, "bs");
|
||||
registerLocaleData(localeCa, "ca");
|
||||
registerLocaleData(localeCs, "cs");
|
||||
registerLocaleData(localeDa, "da");
|
||||
registerLocaleData(localeDe, "de");
|
||||
registerLocaleData(localeEl, "el");
|
||||
registerLocaleData(localeEnGb, "en-GB");
|
||||
registerLocaleData(localeEnIn, "en-IN");
|
||||
registerLocaleData(localeEo, "eo");
|
||||
registerLocaleData(localeEs, "es");
|
||||
registerLocaleData(localeEt, "et");
|
||||
registerLocaleData(localeFi, "fi");
|
||||
registerLocaleData(localeFil, "fil");
|
||||
registerLocaleData(localeFr, "fr");
|
||||
registerLocaleData(localeHe, "he");
|
||||
registerLocaleData(localeHi, "hi");
|
||||
registerLocaleData(localeHr, "hr");
|
||||
registerLocaleData(localeHu, "hu");
|
||||
registerLocaleData(localeId, "id");
|
||||
registerLocaleData(localeIt, "it");
|
||||
registerLocaleData(localeJa, "ja");
|
||||
registerLocaleData(localeKa, "ka");
|
||||
registerLocaleData(localeKm, "km");
|
||||
registerLocaleData(localeKn, "kn");
|
||||
registerLocaleData(localeKo, "ko");
|
||||
registerLocaleData(localeLv, "lv");
|
||||
registerLocaleData(localeMl, "ml");
|
||||
registerLocaleData(localeNb, "nb");
|
||||
registerLocaleData(localeNl, "nl");
|
||||
registerLocaleData(localeNn, "nn");
|
||||
registerLocaleData(localePl, "pl");
|
||||
registerLocaleData(localePtBr, "pt-BR");
|
||||
registerLocaleData(localePtPt, "pt-PT");
|
||||
registerLocaleData(localeRo, "ro");
|
||||
registerLocaleData(localeRu, "ru");
|
||||
registerLocaleData(localeSi, "si");
|
||||
registerLocaleData(localeSk, "sk");
|
||||
registerLocaleData(localeSl, "sl");
|
||||
registerLocaleData(localeSr, "sr");
|
||||
registerLocaleData(localeSv, "sv");
|
||||
registerLocaleData(localeTr, "tr");
|
||||
registerLocaleData(localeUk, "uk");
|
||||
registerLocaleData(localeVi, "vi");
|
||||
registerLocaleData(localeZhCn, "zh-CN");
|
||||
registerLocaleData(localeZhTw, "zh-TW");
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
DragDropModule,
|
||||
FormsModule,
|
||||
InfiniteScrollModule,
|
||||
JslibModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
BadgeModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
ToastrModule,
|
||||
BadgeModule,
|
||||
ButtonModule,
|
||||
MenuModule,
|
||||
],
|
||||
exports: [
|
||||
CommonModule,
|
||||
DragDropModule,
|
||||
FormsModule,
|
||||
InfiniteScrollModule,
|
||||
JslibModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
BadgeModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
ToastrModule,
|
||||
BadgeModule,
|
||||
ButtonModule,
|
||||
MenuModule,
|
||||
],
|
||||
providers: [DatePipe],
|
||||
bootstrap: [],
|
||||
})
|
||||
export class SharedModule {}
|
@ -0,0 +1,74 @@
|
||||
<ng-container *ngIf="show">
|
||||
<div class="filter-heading">
|
||||
<button
|
||||
(click)="toggleCollapse(collectionsGrouping)"
|
||||
[attr.aria-expanded]="!isCollapsed(collectionsGrouping)"
|
||||
aria-controls="collection-filters"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
class="toggle-button"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(collectionsGrouping),
|
||||
'bwi-angle-down': !isCollapsed(collectionsGrouping)
|
||||
}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<h3 class="filter-title">{{ collectionsGrouping.name | i18n }}</h3>
|
||||
</div>
|
||||
<ul id="collection-filters" *ngIf="!isCollapsed(collectionsGrouping)" class="filter-options">
|
||||
<ng-template #recursiveCollections let-collections>
|
||||
<li
|
||||
*ngFor="let c of collections"
|
||||
[ngClass]="{
|
||||
active: c.node.id === activeFilter.selectedCollectionId
|
||||
}"
|
||||
class="filter-option"
|
||||
>
|
||||
<span class="filter-buttons">
|
||||
<button
|
||||
class="toggle-button"
|
||||
*ngIf="c.children.length"
|
||||
(click)="collapse(c.node)"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
[attr.aria-expanded]="!isCollapsed(c.node)"
|
||||
[attr.aria-controls]="c.node.name + '_children'"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(c.node),
|
||||
'bwi-angle-down': !isCollapsed(c.node)
|
||||
}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<button class="filter-button" (click)="applyFilter(c.node)">
|
||||
<i
|
||||
*ngIf="c.children.length === 0"
|
||||
class="bwi bwi-collection bwi-fw"
|
||||
aria-hidden="true"
|
||||
></i
|
||||
>{{ c.node.name }}
|
||||
</button>
|
||||
</span>
|
||||
<ul
|
||||
[id]="c.node.name + '_children'"
|
||||
class="nested-filter-options"
|
||||
*ngIf="c.children.length && !isCollapsed(c.node)"
|
||||
>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"
|
||||
>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-template>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="recursiveCollections; context: { $implicit: nestedCollections }"
|
||||
>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</ng-container>
|
@ -0,0 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { CollectionFilterComponent as BaseCollectionFilterComponent } from "jslib-angular/modules/vault-filter/components/collection-filter.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-collection-filter",
|
||||
templateUrl: "collection-filter.component.html",
|
||||
})
|
||||
export class CollectionFilterComponent extends BaseCollectionFilterComponent {}
|
@ -0,0 +1,84 @@
|
||||
<ng-container *ngIf="!hide && !activeFilter.selectedOrganizationId">
|
||||
<div class="filter-heading">
|
||||
<button
|
||||
class="toggle-button"
|
||||
(click)="toggleCollapse(foldersGrouping)"
|
||||
[attr.aria-expanded]="!isCollapsed(foldersGrouping)"
|
||||
aria-controls="folder-filters"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(foldersGrouping),
|
||||
'bwi-angle-down': !isCollapsed(foldersGrouping)
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
<h3 class="filter-title">
|
||||
{{ "folders" | i18n }}
|
||||
</h3>
|
||||
<button
|
||||
class="text-muted ml-auto add-button"
|
||||
(click)="addFolder()"
|
||||
appA11yTitle="{{ 'addFolder' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<ul id="folder-filters" *ngIf="!isCollapsed(foldersGrouping)" class="filter-options">
|
||||
<ng-template #recursiveFolders let-folders>
|
||||
<li
|
||||
*ngFor="let f of folders"
|
||||
[ngClass]="{
|
||||
active: f.node.id === activeFilter.selectedFolderId && activeFilter.selectedFolder
|
||||
}"
|
||||
class="filter-option"
|
||||
>
|
||||
<span class="filter-buttons">
|
||||
<button
|
||||
*ngIf="f.children.length"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
(click)="toggleCollapse(f.node)"
|
||||
[attr.aria-expanded]="!isCollapsed(f.node)"
|
||||
[attr.aria-controls]="f.node.name + '_children'"
|
||||
class="toggle-button"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(f.node),
|
||||
'bwi-angle-down': !isCollapsed(f.node)
|
||||
}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<button class="filter-button" (click)="applyFilter(f.node)">
|
||||
<i *ngIf="f.children.length === 0" class="bwi bwi-fw bwi-folder" aria-hidden="true"></i
|
||||
>{{ f.node.name }}
|
||||
</button>
|
||||
<button
|
||||
class="edit-button"
|
||||
(click)="editFolder(f.node)"
|
||||
appA11yTitle="{{ 'editFolder' | i18n }}"
|
||||
*ngIf="f.node.id"
|
||||
>
|
||||
<i class="bwi bwi-pencil bwi-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
<ul
|
||||
[id]="f.node.name + '_children'"
|
||||
class="nested-filter-options"
|
||||
*ngIf="f.children.length && !isCollapsed(f.node)"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }">
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-template>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }"
|
||||
></ng-container>
|
||||
</ul>
|
||||
</ng-container>
|
@ -0,0 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { FolderFilterComponent as BaseFolderFilterComponent } from "jslib-angular/modules/vault-filter/components/folder-filter.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-folder-filter",
|
||||
templateUrl: "folder-filter.component.html",
|
||||
})
|
||||
export class FolderFilterComponent extends BaseFolderFilterComponent {}
|
@ -0,0 +1,149 @@
|
||||
<ng-container *ngIf="!hide">
|
||||
<ng-container [ngSwitch]="displayMode">
|
||||
<ng-container *ngSwitchCase="'noOrganizations'">
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option active">
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button">
|
||||
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
|
||||
{{ "myVault" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'personalOwnershipPolicy'">
|
||||
<div class="filter-heading">
|
||||
<button
|
||||
(click)="toggleCollapse()"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
class="toggle-button"
|
||||
[attr.aria-expanded]="!isCollapsed"
|
||||
aria-controls="organization-filters"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed,
|
||||
'bwi-angle-down': !isCollapsed
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
<button
|
||||
class="filter-button"
|
||||
(click)="clearFilter()"
|
||||
[ngClass]="{ active: !hasActiveFilter }"
|
||||
>
|
||||
{{ organizationGrouping.name | i18n }}
|
||||
</button>
|
||||
<a
|
||||
href="#"
|
||||
routerLink="/settings/create-organization"
|
||||
class="text-muted ml-auto create-organization-link"
|
||||
appA11yTitle="{{ 'addOrganization' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<ul id="organization-filters" *ngIf="!isCollapsed" class="filter-options">
|
||||
<li
|
||||
class="filter-option"
|
||||
*ngFor="let organization of organizations"
|
||||
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
|
||||
>
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" (click)="applyOrganizationFilter(organization)">
|
||||
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
|
||||
{{ organization.name }}
|
||||
</button>
|
||||
<ng-container *ngIf="organization.id === activeFilter.selectedOrganizationId">
|
||||
<button [bitMenuTriggerFor]="orgMenu" class="org-options ml-auto">
|
||||
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu class="filter-organization-options" #orgMenu>
|
||||
<app-organization-options [organization]="organization"></app-organization-options>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'singleOrganizationAndPersonalOwnershipPolicies'">
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option active">
|
||||
<button class="filter-button">
|
||||
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
|
||||
{{ organizations[0].name }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'organizationMember'">
|
||||
<div class="filter-heading">
|
||||
<button
|
||||
class="toggle-button"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
(click)="toggleCollapse()"
|
||||
[attr.aria-expanded]="!isCollapsed"
|
||||
aria-controls="organization-filters"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed,
|
||||
'bwi-angle-down': !isCollapsed
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
<button
|
||||
class="filter-button"
|
||||
(click)="clearFilter()"
|
||||
[ngClass]="{ active: !hasActiveFilter }"
|
||||
>
|
||||
{{ organizationGrouping.name | i18n }}
|
||||
</button>
|
||||
<a
|
||||
href="#"
|
||||
routerLink="/settings/create-organization"
|
||||
class="text-muted ml-auto create-organization-link"
|
||||
appA11yTitle="{{ 'addOrganization' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<ul id="organization-filters" *ngIf="!isCollapsed" class="filter-options">
|
||||
<li class="filter-option" [ngClass]="{ active: activeFilter.myVaultOnly }">
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" (click)="applyMyVaultFilter()">
|
||||
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
|
||||
{{ "myVault" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
class="filter-option"
|
||||
*ngFor="let organization of organizations"
|
||||
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
|
||||
>
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" (click)="applyOrganizationFilter(organization)">
|
||||
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
|
||||
{{ organization.name }}
|
||||
</button>
|
||||
<ng-container *ngIf="organization.id === activeFilter.selectedOrganizationId">
|
||||
<button [bitMenuTriggerFor]="orgMenu" class="org-options ml-auto">
|
||||
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu class="filter-organization-options" #orgMenu>
|
||||
<app-organization-options [organization]="organization"></app-organization-options>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<hr />
|
||||
</ng-container>
|
@ -0,0 +1,11 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { OrganizationFilterComponent as BaseOrganizationFilterComponent } from "jslib-angular/modules/vault-filter/components/organization-filter.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-organization-filter",
|
||||
templateUrl: "organization-filter.component.html",
|
||||
})
|
||||
export class OrganizationFilterComponent extends BaseOrganizationFilterComponent {
|
||||
displayText = "allVaults";
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted tw-m-2"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<div *ngIf="loaded" class="tw-max-w-[300px] tw-min-w-[200px] tw-flex tw-flex-col">
|
||||
<button
|
||||
*ngIf="allowEnrollmentChanges(organization) && !organization.resetPasswordEnrolled"
|
||||
class="dropdown-item"
|
||||
(click)="toggleResetPasswordEnrollment(organization)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
||||
{{ "enrollPasswordReset" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
*ngIf="allowEnrollmentChanges(organization) && organization.resetPasswordEnrolled"
|
||||
class="dropdown-item"
|
||||
(click)="toggleResetPasswordEnrollment(organization)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
|
||||
{{ "withdrawPasswordReset" | i18n }}
|
||||
</button>
|
||||
<ng-container *ngIf="organization.useSso && organization.identifier">
|
||||
<button
|
||||
*ngIf="organization.ssoBound; else linkSso"
|
||||
class="dropdown-item"
|
||||
(click)="unlinkSso(organization)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-chain-broken" aria-hidden="true"></i>
|
||||
{{ "unlinkSso" | i18n }}
|
||||
</button>
|
||||
<ng-template #linkSso>
|
||||
<app-link-sso [organization]="organization"> </app-link-sso>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<button class="dropdown-item text-danger" (click)="leave(organization)">
|
||||
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
|
||||
{{ "leave" | i18n }}
|
||||
</button>
|
||||
</div>
|
@ -1,10 +1,9 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
@ -15,19 +14,17 @@ import { Policy } from "jslib-common/models/domain/policy";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
|
||||
|
||||
@Component({
|
||||
selector: "app-organizations",
|
||||
templateUrl: "organizations.component.html",
|
||||
selector: "app-organization-options",
|
||||
templateUrl: "organization-options.component.html",
|
||||
})
|
||||
export class OrganizationsComponent implements OnInit {
|
||||
@Input() vault = false;
|
||||
|
||||
organizations: Organization[];
|
||||
export class OrganizationOptionsComponent {
|
||||
actionPromise: Promise<any>;
|
||||
policies: Policy[];
|
||||
loaded = false;
|
||||
actionPromise: Promise<any>;
|
||||
|
||||
@Input() organization: Organization;
|
||||
|
||||
constructor(
|
||||
private organizationService: OrganizationService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private apiService: ApiService,
|
||||
@ -38,16 +35,11 @@ export class OrganizationsComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
if (!this.vault) {
|
||||
await this.syncService.fullSync(true);
|
||||
await this.load();
|
||||
}
|
||||
await this.syncService.fullSync(true);
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async load() {
|
||||
const orgs = await this.organizationService.getAll();
|
||||
orgs.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
this.organizations = orgs;
|
||||
this.policies = await this.policyService.getAll(PolicyType.ResetPassword);
|
||||
this.loaded = true;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<ng-container *ngIf="show">
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option" [ngClass]="{ active: activeFilter.status === 'all' }">
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" (click)="applyFilter('all')">
|
||||
<i class="bwi bwi-fw bwi-filter" aria-hidden="true"></i> {{ "allItems" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
*ngIf="!hideFavorites"
|
||||
class="filter-option"
|
||||
[ngClass]="{ active: activeFilter.status === 'favorites' }"
|
||||
>
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" (click)="applyFilter('favorites')">
|
||||
<i class="bwi bwi-fw bwi-star" aria-hidden="true"></i> {{ "favorites" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
*ngIf="!hideTrash"
|
||||
class="filter-option"
|
||||
[ngClass]="{ active: activeFilter.status === 'trash' }"
|
||||
>
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" (click)="applyFilter('trash')">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i> {{ "trash" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
@ -0,0 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { StatusFilterComponent as BaseStatusFilterComponent } from "jslib-angular/modules/vault-filter/components/status-filter.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-status-filter",
|
||||
templateUrl: "status-filter.component.html",
|
||||
})
|
||||
export class StatusFilterComponent extends BaseStatusFilterComponent {}
|
@ -0,0 +1,60 @@
|
||||
<div class="filter-heading">
|
||||
<button
|
||||
class="toggle-button"
|
||||
[attr.aria-expanded]="!isCollapsed"
|
||||
aria-controls="type-filters"
|
||||
(click)="toggleCollapse()"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed,
|
||||
'bwi-angle-down': !isCollapsed
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
<h3>
|
||||
{{ "types" | i18n }}
|
||||
</h3>
|
||||
</div>
|
||||
<ul id="type-filters" *ngIf="!isCollapsed" class="filter-options">
|
||||
<li
|
||||
class="filter-option"
|
||||
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Login }"
|
||||
>
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Login)">
|
||||
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>{{ "typeLogin" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li class="filter-option" [ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Card }">
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Card)">
|
||||
<i class="bwi bwi-fw bwi-credit-card" aria-hidden="true"></i>{{ "typeCard" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
class="filter-option"
|
||||
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Identity }"
|
||||
>
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Identity)">
|
||||
<i class="bwi bwi-fw bwi-id-card" aria-hidden="true"></i>{{ "typeIdentity" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
class="filter-option"
|
||||
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.SecureNote }"
|
||||
>
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.SecureNote)">
|
||||
<i class="bwi bwi-fw bwi-sticky-note" aria-hidden="true"></i>{{ "typeSecureNote" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
@ -0,0 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { TypeFilterComponent as BaseTypeFilterComponent } from "jslib-angular/modules/vault-filter/components/type-filter.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-type-filter",
|
||||
templateUrl: "type-filter.component.html",
|
||||
})
|
||||
export class TypeFilterComponent extends BaseTypeFilterComponent {}
|
82
src/app/modules/vault-filter/vault-filter.component.html
Normal file
82
src/app/modules/vault-filter/vault-filter.component.html
Normal file
@ -0,0 +1,82 @@
|
||||
<div class="card vault-filters">
|
||||
<div class="container loading-spinner" *ngIf="!isLoaded">
|
||||
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div *ngIf="isLoaded">
|
||||
<div class="card-header d-flex">
|
||||
{{ "filters" | i18n }}
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://bitwarden.com/help/searching-vault/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="{{ (searchPlaceholder | i18n) || ('searchVault' | i18n) }}"
|
||||
id="search"
|
||||
class="form-control"
|
||||
[(ngModel)]="searchText"
|
||||
(input)="searchTextChanged()"
|
||||
autocomplete="off"
|
||||
appAutofocus
|
||||
/>
|
||||
<div class="filter">
|
||||
<app-organization-filter
|
||||
*ngIf="showOrgFilter"
|
||||
[hide]="hideOrganizations"
|
||||
[activeFilter]="activeFilter"
|
||||
[collapsedFilterNodes]="collapsedFilterNodes"
|
||||
[organizations]="organizations"
|
||||
[activePersonalOwnershipPolicy]="activePersonalOwnershipPolicy"
|
||||
[activeSingleOrganizationPolicy]="activeSingleOrganizationPolicy"
|
||||
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
|
||||
(onFilterChange)="applyFilter($event)"
|
||||
></app-organization-filter>
|
||||
</div>
|
||||
<div class="filter">
|
||||
<app-status-filter
|
||||
[hideFavorites]="!showFavorites"
|
||||
[hideTrash]="hideTrash"
|
||||
[activeFilter]="activeFilter"
|
||||
(onFilterChange)="applyFilter($event)"
|
||||
></app-status-filter>
|
||||
</div>
|
||||
<div class="filter">
|
||||
<app-type-filter
|
||||
[activeFilter]="activeFilter"
|
||||
[collapsedFilterNodes]="collapsedFilterNodes"
|
||||
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
|
||||
(onFilterChange)="applyFilter($event)"
|
||||
></app-type-filter>
|
||||
</div>
|
||||
<div class="filter">
|
||||
<app-folder-filter
|
||||
[hide]="!showFolders"
|
||||
[activeFilter]="activeFilter"
|
||||
[collapsedFilterNodes]="collapsedFilterNodes"
|
||||
[folderNodes]="folders"
|
||||
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
|
||||
(onFilterChange)="applyFilter($event)"
|
||||
(onAddFolder)="addFolder()"
|
||||
(onEditFolder)="editFolder($event)"
|
||||
></app-folder-filter>
|
||||
</div>
|
||||
<div class="filter">
|
||||
<app-collection-filter
|
||||
[hide]="hideCollections"
|
||||
[activeFilter]="activeFilter"
|
||||
[collapsedFilterNodes]="collapsedFilterNodes"
|
||||
[collectionNodes]="collections"
|
||||
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
|
||||
(onFilterChange)="applyFilter($event)"
|
||||
></app-collection-filter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
36
src/app/modules/vault-filter/vault-filter.component.ts
Normal file
36
src/app/modules/vault-filter/vault-filter.component.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { VaultFilterComponent as BaseVaultFilterComponent } from "jslib-angular/modules/vault-filter/vault-filter.component";
|
||||
import { VaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
|
||||
@Component({
|
||||
selector: "app-vault-filter",
|
||||
templateUrl: "vault-filter.component.html",
|
||||
})
|
||||
export class VaultFilterComponent extends BaseVaultFilterComponent {
|
||||
@Input() showOrgFilter = true;
|
||||
@Input() showFolders = true;
|
||||
@Input() showFavorites = true;
|
||||
|
||||
@Output() onSearchTextChanged = new EventEmitter<string>();
|
||||
|
||||
searchPlaceholder: string;
|
||||
searchText = "";
|
||||
|
||||
organization: Organization;
|
||||
|
||||
constructor(vaultFilterService: VaultFilterService) {
|
||||
super(vaultFilterService);
|
||||
}
|
||||
|
||||
searchTextChanged() {
|
||||
this.onSearchTextChanged.emit(this.searchText);
|
||||
}
|
||||
|
||||
// This method exists because the vault component gets its data mixed up during the initial sync on first login. It looks for data before the sync is complete.
|
||||
// It should be removed as soon as doing so makes sense.
|
||||
async reloadOrganizations() {
|
||||
this.organizations = await this.vaultFilterService.buildOrganizations();
|
||||
}
|
||||
}
|
48
src/app/modules/vault-filter/vault-filter.module.ts
Normal file
48
src/app/modules/vault-filter/vault-filter.module.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { VaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { SharedModule } from "../shared.module";
|
||||
|
||||
import { CollectionFilterComponent } from "./components/collection-filter.component";
|
||||
import { FolderFilterComponent } from "./components/folder-filter.component";
|
||||
import { OrganizationFilterComponent } from "./components/organization-filter.component";
|
||||
import { OrganizationOptionsComponent } from "./components/organization-options.component";
|
||||
import { StatusFilterComponent } from "./components/status-filter.component";
|
||||
import { TypeFilterComponent } from "./components/type-filter.component";
|
||||
import { VaultFilterComponent } from "./vault-filter.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule],
|
||||
declarations: [
|
||||
VaultFilterComponent,
|
||||
CollectionFilterComponent,
|
||||
FolderFilterComponent,
|
||||
OrganizationFilterComponent,
|
||||
OrganizationOptionsComponent,
|
||||
StatusFilterComponent,
|
||||
TypeFilterComponent,
|
||||
],
|
||||
exports: [VaultFilterComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: VaultFilterService,
|
||||
useClass: VaultFilterService,
|
||||
deps: [
|
||||
StateService,
|
||||
OrganizationService,
|
||||
FolderService,
|
||||
CipherService,
|
||||
CollectionService,
|
||||
PolicyService,
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class VaultFilterModule {}
|
3
src/app/modules/vault-filter/vault-filter.service.ts
Normal file
3
src/app/modules/vault-filter/vault-filter.service.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { VaultFilterService as BaseVaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
|
||||
|
||||
export class VaultFilterService extends BaseVaultFilterService {}
|
@ -0,0 +1,16 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { IndividualVaultComponent } from "./individual-vault.component";
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: IndividualVaultComponent,
|
||||
data: { titleId: "vaults" },
|
||||
},
|
||||
];
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class IndividualVaultRoutingModule {}
|
@ -1,23 +1,25 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<app-vault-groupings
|
||||
(onAllClicked)="clearGroupingFilters()"
|
||||
(onFavoritesClicked)="filterFavorites()"
|
||||
(onCipherTypeClicked)="filterCipherType($event)"
|
||||
(onFolderClicked)="filterFolder($event.id)"
|
||||
(onAddFolder)="addFolder()"
|
||||
(onEditFolder)="editFolder($event.id)"
|
||||
(onCollectionClicked)="filterCollection($event.id)"
|
||||
(onSearchTextChanged)="filterSearchText($event)"
|
||||
(onTrashClicked)="filterDeleted()"
|
||||
>
|
||||
</app-vault-groupings>
|
||||
<div class="groupings">
|
||||
<div class="content">
|
||||
<div class="inner-content">
|
||||
<app-vault-filter
|
||||
#vaultFilter
|
||||
[activeFilter]="activeFilter"
|
||||
(onFilterChange)="applyVaultFilter($event)"
|
||||
(onAddFolder)="addFolder()"
|
||||
(onEditFolder)="editFolder($event.id)"
|
||||
(onSearchTextChanged)="filterSearchText($event)"
|
||||
></app-vault-filter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div [ngClass]="{ 'col-6': isShowingCards, 'col-9': !isShowingCards }">
|
||||
<div class="page-header d-flex">
|
||||
<h1>
|
||||
{{ "myVault" | i18n }}
|
||||
{{ "vaultItems" | i18n }}
|
||||
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
|
||||
<ng-container *ngIf="actionSpinner.loading">
|
||||
<i
|
||||
@ -52,6 +54,7 @@
|
||||
(onShareClicked)="shareCipher($event)"
|
||||
(onCollectionsClicked)="editCipherCollections($event)"
|
||||
(onCloneClicked)="cloneCipher($event)"
|
||||
(onOrganzationBadgeClicked)="applyOrganizationFilter($event)"
|
||||
>
|
||||
</app-vault-ciphers>
|
||||
</div>
|
||||
@ -97,40 +100,6 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex">
|
||||
{{ "organizations" | i18n }}
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://bitwarden.com/help/about-organizations/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<app-organizations [vault]="true"></app-organizations>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-4" *ngIf="showProviders">
|
||||
<div class="card-header d-flex">
|
||||
{{ "providers" | i18n }}
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://bitwarden.com/help/providers/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<app-providers vault="true"></app-providers>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -10,6 +10,7 @@ import {
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
@ -19,35 +20,31 @@ import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
import { OrganizationsComponent } from "../settings/organizations.component";
|
||||
import { UpdateKeyComponent } from "../settings/update-key.component";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
import { AttachmentsComponent } from "./attachments.component";
|
||||
import { CiphersComponent } from "./ciphers.component";
|
||||
import { CollectionsComponent } from "./collections.component";
|
||||
import { FolderAddEditComponent } from "./folder-add-edit.component";
|
||||
import { GroupingsComponent } from "./groupings.component";
|
||||
import { ShareComponent } from "./share.component";
|
||||
import { UpdateKeyComponent } from "../../../../settings/update-key.component";
|
||||
import { AddEditComponent } from "../../../../vault/add-edit.component";
|
||||
import { AttachmentsComponent } from "../../../../vault/attachments.component";
|
||||
import { CiphersComponent } from "../../../../vault/ciphers.component";
|
||||
import { CollectionsComponent } from "../../../../vault/collections.component";
|
||||
import { FolderAddEditComponent } from "../../../../vault/folder-add-edit.component";
|
||||
import { ShareComponent } from "../../../../vault/share.component";
|
||||
import { VaultFilterComponent } from "../../../vault-filter/vault-filter.component";
|
||||
import { VaultService } from "../../vault.service";
|
||||
|
||||
const BroadcasterSubscriptionId = "VaultComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-vault",
|
||||
templateUrl: "vault.component.html",
|
||||
templateUrl: "individual-vault.component.html",
|
||||
})
|
||||
export class VaultComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
|
||||
export class IndividualVaultComponent implements OnInit, OnDestroy {
|
||||
@ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent;
|
||||
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
|
||||
@ViewChild(OrganizationsComponent, { static: true })
|
||||
organizationsComponent: OrganizationsComponent;
|
||||
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
||||
attachmentsModalRef: ViewContainerRef;
|
||||
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
|
||||
@ -64,13 +61,15 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
type: CipherType = null;
|
||||
folderId: string = null;
|
||||
collectionId: string = null;
|
||||
organizationId: string = null;
|
||||
myVaultOnly = false;
|
||||
showVerifyEmail = false;
|
||||
showBrowserOutdated = false;
|
||||
showUpdateKey = false;
|
||||
showPremiumCallout = false;
|
||||
showProviders = false;
|
||||
deleted = false;
|
||||
trashCleanupWarning: string = null;
|
||||
activeFilter: VaultFilter = new VaultFilter();
|
||||
|
||||
constructor(
|
||||
private syncService: SyncService,
|
||||
@ -87,7 +86,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
private ngZone: NgZone,
|
||||
private stateService: StateService,
|
||||
private organizationService: OrganizationService,
|
||||
private providerService: ProviderService,
|
||||
private vaultService: VaultService,
|
||||
private cipherService: CipherService,
|
||||
private passwordRepromptService: PasswordRepromptService
|
||||
) {}
|
||||
@ -103,42 +102,24 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||
await this.syncService.fullSync(false);
|
||||
|
||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
this.showPremiumCallout =
|
||||
!this.showVerifyEmail && !canAccessPremium && !this.platformUtilsService.isSelfHost();
|
||||
|
||||
this.showProviders = (await this.providerService.getAll()).length > 0;
|
||||
|
||||
await Promise.all([this.groupingsComponent.load(), this.organizationsComponent.load()]);
|
||||
this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
|
||||
this.filterComponent.reloadOrganizations();
|
||||
this.showUpdateKey = !(await this.cryptoService.hasEncKey());
|
||||
|
||||
if (params == null) {
|
||||
this.groupingsComponent.selectedAll = true;
|
||||
await this.ciphersComponent.reload();
|
||||
} else {
|
||||
if (params.deleted) {
|
||||
this.groupingsComponent.selectedTrash = true;
|
||||
await this.filterDeleted();
|
||||
} else if (params.favorites) {
|
||||
this.groupingsComponent.selectedFavorites = true;
|
||||
await this.filterFavorites();
|
||||
} else if (params.type) {
|
||||
const t = parseInt(params.type, null);
|
||||
this.groupingsComponent.selectedType = t;
|
||||
await this.filterCipherType(t);
|
||||
} else if (params.folderId) {
|
||||
this.groupingsComponent.selectedFolder = true;
|
||||
this.groupingsComponent.selectedFolderId = params.folderId;
|
||||
await this.filterFolder(params.folderId);
|
||||
} else if (params.collectionId) {
|
||||
this.groupingsComponent.selectedCollectionId = params.collectionId;
|
||||
await this.filterCollection(params.collectionId);
|
||||
} else {
|
||||
this.groupingsComponent.selectedAll = true;
|
||||
await this.ciphersComponent.reload();
|
||||
if (params.cipherId) {
|
||||
const cipherView = new CipherView();
|
||||
cipherView.id = params.cipherId;
|
||||
if (params.action === "clone") {
|
||||
await this.cloneCipher(cipherView);
|
||||
} else if (params.action === "edit") {
|
||||
await this.editCipher(cipherView);
|
||||
}
|
||||
}
|
||||
await this.ciphersComponent.reload();
|
||||
|
||||
this.route.queryParams.subscribe(async (params) => {
|
||||
if (params.cipherId) {
|
||||
@ -164,8 +145,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
case "syncCompleted":
|
||||
if (message.successfully) {
|
||||
await Promise.all([
|
||||
this.groupingsComponent.load(),
|
||||
this.organizationsComponent.load(),
|
||||
this.filterComponent.reloadCollectionsAndFolders(this.activeFilter),
|
||||
this.filterComponent.reloadOrganizations(),
|
||||
this.ciphersComponent.load(this.ciphersComponent.filter),
|
||||
]);
|
||||
this.changeDetectorRef.detectChanges();
|
||||
@ -177,72 +158,78 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
get isShowingCards() {
|
||||
return (
|
||||
this.showBrowserOutdated ||
|
||||
this.showPremiumCallout ||
|
||||
this.showUpdateKey ||
|
||||
this.showVerifyEmail
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
async clearGroupingFilters() {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
|
||||
await this.ciphersComponent.reload();
|
||||
this.clearFilters();
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterFavorites() {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchFavorites");
|
||||
await this.ciphersComponent.reload((c) => c.favorite);
|
||||
this.clearFilters();
|
||||
this.favorites = true;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterDeleted() {
|
||||
this.ciphersComponent.showAddNew = false;
|
||||
this.ciphersComponent.deleted = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchTrash");
|
||||
await this.ciphersComponent.reload(null, true);
|
||||
this.clearFilters();
|
||||
this.deleted = true;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterCipherType(type: CipherType) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchType");
|
||||
await this.ciphersComponent.reload((c) => c.type === type);
|
||||
this.clearFilters();
|
||||
this.type = type;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterFolder(folderId: string) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
folderId = folderId === "none" ? null : folderId;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchFolder");
|
||||
await this.ciphersComponent.reload((c) => c.folderId === folderId);
|
||||
this.clearFilters();
|
||||
this.folderId = folderId == null ? "none" : folderId;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterCollection(collectionId: string) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchCollection");
|
||||
await this.ciphersComponent.reload(
|
||||
(c) => c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1
|
||||
async applyVaultFilter(vaultFilter: VaultFilter) {
|
||||
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
|
||||
this.activeFilter = vaultFilter;
|
||||
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
|
||||
this.filterComponent.searchPlaceholder = this.vaultService.calculateSearchBarLocalizationString(
|
||||
this.activeFilter
|
||||
);
|
||||
this.clearFilters();
|
||||
this.collectionId = collectionId;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async applyOrganizationFilter(orgId: string) {
|
||||
if (orgId == null) {
|
||||
this.activeFilter.resetOrganization();
|
||||
this.activeFilter.myVaultOnly = true;
|
||||
} else {
|
||||
this.activeFilter.selectedOrganizationId = orgId;
|
||||
}
|
||||
await this.applyVaultFilter(this.activeFilter);
|
||||
}
|
||||
|
||||
filterSearchText(searchText: string) {
|
||||
this.ciphersComponent.searchText = searchText;
|
||||
this.ciphersComponent.search(200);
|
||||
}
|
||||
|
||||
private buildFilter(): (cipher: CipherView) => boolean {
|
||||
return (cipher) => {
|
||||
let cipherPassesFilter = true;
|
||||
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.favorite;
|
||||
}
|
||||
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.isDeleted;
|
||||
}
|
||||
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
|
||||
}
|
||||
if (
|
||||
this.activeFilter.selectedFolderId != null &&
|
||||
this.activeFilter.selectedFolderId != "none" &&
|
||||
cipherPassesFilter
|
||||
) {
|
||||
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
|
||||
}
|
||||
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
|
||||
cipherPassesFilter =
|
||||
cipher.collectionIds != null &&
|
||||
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
|
||||
}
|
||||
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
|
||||
}
|
||||
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.organizationId === null;
|
||||
}
|
||||
return cipherPassesFilter;
|
||||
};
|
||||
}
|
||||
|
||||
async editCipherAttachments(cipher: CipherView) {
|
||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
if (cipher.organizationId == null && !canAccessPremium) {
|
||||
@ -314,7 +301,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
comp.folderId = null;
|
||||
comp.onSavedFolder.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.groupingsComponent.loadFolders();
|
||||
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -328,13 +315,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
comp.folderId = folderId;
|
||||
comp.onSavedFolder.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.groupingsComponent.loadFolders();
|
||||
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
|
||||
});
|
||||
comp.onDeletedFolder.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.groupingsComponent.loadFolders();
|
||||
await this.filterFolder("none");
|
||||
this.groupingsComponent.selectedFolderId = null;
|
||||
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -344,15 +329,21 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
const component = await this.editCipher(null);
|
||||
component.type = this.type;
|
||||
component.folderId = this.folderId === "none" ? null : this.folderId;
|
||||
if (this.collectionId != null) {
|
||||
const collection = this.groupingsComponent.collections.filter(
|
||||
(c) => c.id === this.collectionId
|
||||
if (this.activeFilter.selectedCollectionId != null) {
|
||||
const collection = this.filterComponent.collections.fullList.filter(
|
||||
(c) => c.id === this.activeFilter.selectedCollectionId
|
||||
);
|
||||
if (collection.length > 0) {
|
||||
component.organizationId = collection[0].organizationId;
|
||||
component.collectionIds = [this.collectionId];
|
||||
component.collectionIds = [this.activeFilter.selectedCollectionId];
|
||||
}
|
||||
}
|
||||
if (this.activeFilter.selectedFolderId && this.activeFilter.selectedFolder) {
|
||||
component.folderId = this.activeFilter.selectedFolderId;
|
||||
}
|
||||
if (this.activeFilter.selectedOrganizationId) {
|
||||
component.organizationId = this.activeFilter.selectedOrganizationId;
|
||||
}
|
||||
}
|
||||
|
||||
async editCipher(cipher: CipherView) {
|
||||
@ -404,14 +395,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
await this.modalService.openViewRef(UpdateKeyComponent, this.updateKeyModalRef);
|
||||
}
|
||||
|
||||
private clearFilters() {
|
||||
this.folderId = null;
|
||||
this.collectionId = null;
|
||||
this.favorites = false;
|
||||
this.type = null;
|
||||
this.deleted = false;
|
||||
}
|
||||
|
||||
private go(queryParams: any = null) {
|
||||
if (queryParams == null) {
|
||||
queryParams = {
|
@ -0,0 +1,13 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { VaultModule } from "../../vault.module";
|
||||
|
||||
import { IndividualVaultRoutingModule } from "./individual-vault-routing.module";
|
||||
import { IndividualVaultComponent } from "./individual-vault.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [VaultModule, IndividualVaultRoutingModule],
|
||||
declarations: [IndividualVaultComponent],
|
||||
exports: [IndividualVaultComponent],
|
||||
})
|
||||
export class IndividualVaultModule {}
|
@ -0,0 +1,12 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SharedModule } from "../../../shared.module";
|
||||
|
||||
import { OrganizationNameBadgeComponent } from "./organization-name-badge.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule],
|
||||
declarations: [OrganizationNameBadgeComponent],
|
||||
exports: [OrganizationNameBadgeComponent],
|
||||
})
|
||||
export class OrganizationBadgeModule {}
|
@ -0,0 +1,9 @@
|
||||
<button
|
||||
bit-badge
|
||||
[style.color]="textColor"
|
||||
[style.background-color]="color"
|
||||
appA11yTitle="{{ organizationName }}"
|
||||
(click)="emitOnOrganizationClicked()"
|
||||
>
|
||||
{{ organizationName | ellipsis: 13 }}
|
||||
</button>
|
@ -0,0 +1,69 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-badge",
|
||||
templateUrl: "organization-name-badge.component.html",
|
||||
})
|
||||
export class OrganizationNameBadgeComponent implements OnInit {
|
||||
@Input() organizationName: string;
|
||||
@Input() color: string;
|
||||
|
||||
@Output() onOrganizationClicked = new EventEmitter<string>();
|
||||
|
||||
textColor: string;
|
||||
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.organizationName == null || this.organizationName === "") {
|
||||
this.organizationName = this.i18nService.t("me");
|
||||
}
|
||||
const upperData = this.organizationName.toUpperCase();
|
||||
if (this.color == null) {
|
||||
this.color = this.stringToColor(upperData);
|
||||
}
|
||||
this.textColor = this.pickTextColorBasedOnBgColor();
|
||||
}
|
||||
|
||||
// This value currently isn't stored anywhere, only calculated in the app-avatar component
|
||||
// Once we are allowing org colors to be changed and saved, change this out
|
||||
private stringToColor(str: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
let color = "#";
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 0xff;
|
||||
color += ("00" + value.toString(16)).substr(-2);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
// There are a few ways to calculate text color for contrast, this one seems to fit accessibility guidelines best.
|
||||
// https://stackoverflow.com/a/3943023/6869691
|
||||
private pickTextColorBasedOnBgColor() {
|
||||
const color = this.color.charAt(0) === "#" ? this.color.substring(1, 7) : this.color;
|
||||
const r = parseInt(color.substring(0, 2), 16); // hexToR
|
||||
const g = parseInt(color.substring(2, 4), 16); // hexToG
|
||||
const b = parseInt(color.substring(4, 6), 16); // hexToB
|
||||
|
||||
const uicolors = [r / 255, g / 255, b / 255];
|
||||
const c = uicolors.map((c) => {
|
||||
if (c <= 0.03928) {
|
||||
return c / 12.92;
|
||||
} else {
|
||||
return Math.pow((c + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
});
|
||||
|
||||
const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
|
||||
return L > 0.179 ? "black !important" : "white !important";
|
||||
}
|
||||
|
||||
emitOnOrganizationClicked() {
|
||||
this.onOrganizationClicked.emit();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { OrganizationVaultComponent } from "./organization-vault.component";
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: OrganizationVaultComponent,
|
||||
data: { titleId: "vaults" },
|
||||
},
|
||||
];
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class OrganizationVaultRoutingModule {}
|
@ -1,22 +1,26 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<app-org-vault-groupings
|
||||
[showFolders]="false"
|
||||
[showFavorites]="false"
|
||||
[showTrash]="true"
|
||||
(onAllClicked)="clearGroupingFilters()"
|
||||
(onCipherTypeClicked)="filterCipherType($event)"
|
||||
(onCollectionClicked)="filterCollection($event.id)"
|
||||
(onSearchTextChanged)="filterSearchText($event)"
|
||||
(onTrashClicked)="filterDeleted()"
|
||||
>
|
||||
</app-org-vault-groupings>
|
||||
<div class="groupings">
|
||||
<div class="content">
|
||||
<div class="inner-content">
|
||||
<app-vault-filter
|
||||
#vaultFilter
|
||||
[showFolders]="false"
|
||||
[showFavorites]="false"
|
||||
[activeFilter]="activeFilter"
|
||||
[showOrgFilter]="false"
|
||||
(onFilterChange)="applyVaultFilter($event)"
|
||||
(onSearchTextChanged)="filterSearchText($event)"
|
||||
></app-vault-filter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<div class="page-header d-flex">
|
||||
<h1>
|
||||
{{ "vault" | i18n }}
|
||||
{{ "vaultItems" | i18n }}
|
||||
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
|
||||
<ng-container *ngIf="actionSpinner.loading">
|
||||
<i
|
@ -10,6 +10,7 @@ import {
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
@ -23,22 +24,22 @@ import { CipherType } from "jslib-common/enums/cipherType";
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
import { EntityEventsComponent } from "../manage/entity-events.component";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
import { AttachmentsComponent } from "./attachments.component";
|
||||
import { CiphersComponent } from "./ciphers.component";
|
||||
import { CollectionsComponent } from "./collections.component";
|
||||
import { GroupingsComponent } from "./groupings.component";
|
||||
import { EntityEventsComponent } from "../../../../organizations/manage/entity-events.component";
|
||||
import { AddEditComponent } from "../../../../organizations/vault/add-edit.component";
|
||||
import { AttachmentsComponent } from "../../../../organizations/vault/attachments.component";
|
||||
import { CiphersComponent } from "../../../../organizations/vault/ciphers.component";
|
||||
import { CollectionsComponent } from "../../../../organizations/vault/collections.component";
|
||||
import { VaultFilterComponent } from "../../../vault-filter/vault-filter.component";
|
||||
import { VaultService } from "../../vault.service";
|
||||
|
||||
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-vault",
|
||||
templateUrl: "vault.component.html",
|
||||
templateUrl: "organization-vault.component.html",
|
||||
})
|
||||
export class VaultComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
|
||||
export class OrganizationVaultComponent implements OnInit, OnDestroy {
|
||||
@ViewChild("vaultFilter", { static: true }) vaultFilterComponent: VaultFilterComponent;
|
||||
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
|
||||
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
||||
attachmentsModalRef: ViewContainerRef;
|
||||
@ -54,6 +55,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
type: CipherType = null;
|
||||
deleted = false;
|
||||
trashCleanupWarning: string = null;
|
||||
activeFilter: VaultFilter = new VaultFilter();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@ -67,6 +69,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private vaultService: VaultService,
|
||||
private cipherService: CipherService,
|
||||
private passwordRepromptService: PasswordRepromptService
|
||||
) {}
|
||||
@ -77,13 +80,13 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
? "trashCleanupWarningSelfHosted"
|
||||
: "trashCleanupWarning"
|
||||
);
|
||||
this.route.parent.params.pipe(first()).subscribe(async (params) => {
|
||||
this.route.parent.params.subscribe(async (params: any) => {
|
||||
this.organization = await this.organizationService.get(params.organizationId);
|
||||
this.groupingsComponent.organization = this.organization;
|
||||
this.vaultFilterComponent.organization = this.organization;
|
||||
this.ciphersComponent.organization = this.organization;
|
||||
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
|
||||
// this.ciphersComponent.searchText = this.vaultFilterComponent.search = qParams.search;
|
||||
if (!this.organization.canViewAllCollections) {
|
||||
await this.syncService.fullSync(false);
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
@ -92,7 +95,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
case "syncCompleted":
|
||||
if (message.successfully) {
|
||||
await Promise.all([
|
||||
this.groupingsComponent.load(),
|
||||
this.vaultFilterComponent.reloadCollectionsAndFolders(
|
||||
new VaultFilter({
|
||||
selectedOrganizationId: this.organization.id,
|
||||
} as Partial<VaultFilter>)
|
||||
),
|
||||
this.ciphersComponent.refresh(),
|
||||
]);
|
||||
this.changeDetectorRef.detectChanges();
|
||||
@ -102,27 +109,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
});
|
||||
}
|
||||
await this.groupingsComponent.load();
|
||||
|
||||
if (qParams == null) {
|
||||
this.groupingsComponent.selectedAll = true;
|
||||
await this.ciphersComponent.reload();
|
||||
} else {
|
||||
if (qParams.deleted) {
|
||||
this.groupingsComponent.selectedTrash = true;
|
||||
await this.filterDeleted(true);
|
||||
} else if (qParams.type) {
|
||||
const t = parseInt(qParams.type, null);
|
||||
this.groupingsComponent.selectedType = t;
|
||||
await this.filterCipherType(t, true);
|
||||
} else if (qParams.collectionId) {
|
||||
this.groupingsComponent.selectedCollectionId = qParams.collectionId;
|
||||
await this.filterCollection(qParams.collectionId, true);
|
||||
} else {
|
||||
this.groupingsComponent.selectedAll = true;
|
||||
await this.ciphersComponent.reload();
|
||||
}
|
||||
}
|
||||
await this.vaultFilterComponent.reloadCollectionsAndFolders(
|
||||
new VaultFilter({ selectedOrganizationId: this.organization.id } as Partial<VaultFilter>)
|
||||
);
|
||||
await this.ciphersComponent.reload();
|
||||
|
||||
if (qParams.viewEvents != null) {
|
||||
const cipher = this.ciphersComponent.ciphers.filter((c) => c.id === qParams.viewEvents);
|
||||
@ -156,63 +146,47 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
async clearGroupingFilters() {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.ciphersComponent.deleted = false;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
|
||||
await this.ciphersComponent.applyFilter();
|
||||
this.clearFilters();
|
||||
async applyVaultFilter(vaultFilter: VaultFilter) {
|
||||
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
|
||||
this.activeFilter = vaultFilter;
|
||||
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
|
||||
this.vaultFilterComponent.searchPlaceholder =
|
||||
this.vaultService.calculateSearchBarLocalizationString(this.activeFilter);
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterCipherType(type: CipherType, load = false) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.ciphersComponent.deleted = false;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchType");
|
||||
const filter = (c: CipherView) => c.type === type;
|
||||
if (load) {
|
||||
await this.ciphersComponent.reload(filter);
|
||||
} else {
|
||||
await this.ciphersComponent.applyFilter(filter);
|
||||
}
|
||||
this.clearFilters();
|
||||
this.type = type;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterCollection(collectionId: string, load = false) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.ciphersComponent.deleted = false;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchCollection");
|
||||
const filter = (c: CipherView) => {
|
||||
if (collectionId === "unassigned") {
|
||||
return c.collectionIds == null || c.collectionIds.length === 0;
|
||||
} else {
|
||||
return c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1;
|
||||
private buildFilter(): (cipher: CipherView) => boolean {
|
||||
return (cipher) => {
|
||||
let cipherPassesFilter = true;
|
||||
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.favorite;
|
||||
}
|
||||
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.isDeleted;
|
||||
}
|
||||
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
|
||||
}
|
||||
if (
|
||||
this.activeFilter.selectedFolderId != null &&
|
||||
this.activeFilter.selectedFolderId != "none" &&
|
||||
cipherPassesFilter
|
||||
) {
|
||||
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
|
||||
}
|
||||
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
|
||||
cipherPassesFilter =
|
||||
cipher.collectionIds != null &&
|
||||
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
|
||||
}
|
||||
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
|
||||
}
|
||||
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.organizationId === null;
|
||||
}
|
||||
return cipherPassesFilter;
|
||||
};
|
||||
if (load) {
|
||||
await this.ciphersComponent.reload(filter);
|
||||
} else {
|
||||
await this.ciphersComponent.applyFilter(filter);
|
||||
}
|
||||
this.clearFilters();
|
||||
this.collectionId = collectionId;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterDeleted(load = false) {
|
||||
this.ciphersComponent.showAddNew = false;
|
||||
this.ciphersComponent.deleted = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchTrash");
|
||||
if (load) {
|
||||
await this.ciphersComponent.reload(null, true);
|
||||
} else {
|
||||
await this.ciphersComponent.applyFilter(null);
|
||||
}
|
||||
this.clearFilters();
|
||||
this.deleted = true;
|
||||
this.go();
|
||||
}
|
||||
|
||||
filterSearchText(searchText: string) {
|
||||
@ -254,7 +228,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
(comp) => {
|
||||
if (this.organization.canEditAnyCollection) {
|
||||
comp.collectionIds = cipher.collectionIds;
|
||||
comp.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
comp.collections = this.vaultFilterComponent.collections.fullList.filter(
|
||||
(c) => !c.readOnly
|
||||
);
|
||||
}
|
||||
comp.organization = this.organization;
|
||||
comp.cipherId = cipher.id;
|
||||
@ -271,7 +247,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
component.organizationId = this.organization.id;
|
||||
component.type = this.type;
|
||||
if (this.organization.canEditAnyCollection) {
|
||||
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
component.collections = this.vaultFilterComponent.collections.fullList.filter(
|
||||
(c) => !c.readOnly
|
||||
);
|
||||
}
|
||||
if (this.collectionId != null) {
|
||||
component.collectionIds = [this.collectionId];
|
||||
@ -324,7 +302,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
component.cloneMode = true;
|
||||
component.organizationId = this.organization.id;
|
||||
if (this.organization.canEditAnyCollection) {
|
||||
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
component.collections = this.vaultFilterComponent.collections.fullList.filter(
|
||||
(c) => !c.readOnly
|
||||
);
|
||||
}
|
||||
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
|
||||
// in the add-edit componenet
|
@ -0,0 +1,13 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { VaultModule } from "../../vault.module";
|
||||
|
||||
import { OrganizationVaultRoutingModule } from "./organization-vault-routing.module";
|
||||
import { OrganizationVaultComponent } from "./organization-vault.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [VaultModule, OrganizationVaultRoutingModule],
|
||||
declarations: [OrganizationVaultComponent],
|
||||
exports: [OrganizationVaultComponent],
|
||||
})
|
||||
export class OrganizationVaultModule {}
|
19
src/app/modules/vault/vault.module.ts
Normal file
19
src/app/modules/vault/vault.module.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { LooseComponentsModule } from "../loose-components.module";
|
||||
import { SharedModule } from "../shared.module";
|
||||
import { VaultFilterModule } from "../vault-filter/vault-filter.module";
|
||||
|
||||
import { VaultService } from "./vault.service";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, VaultFilterModule, LooseComponentsModule],
|
||||
exports: [SharedModule, VaultFilterModule, LooseComponentsModule],
|
||||
providers: [
|
||||
{
|
||||
provide: VaultService,
|
||||
useClass: VaultService,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class VaultModule {}
|
29
src/app/modules/vault/vault.service.ts
Normal file
29
src/app/modules/vault/vault.service.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
|
||||
|
||||
export class VaultService {
|
||||
calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string {
|
||||
if (vaultFilter.status === "favorites") {
|
||||
return "searchFavorites";
|
||||
}
|
||||
if (vaultFilter.status === "trash") {
|
||||
return "searchTrash";
|
||||
}
|
||||
if (vaultFilter.cipherType != null) {
|
||||
return "searchType";
|
||||
}
|
||||
if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId != "none") {
|
||||
return "searchFolder";
|
||||
}
|
||||
if (vaultFilter.selectedCollectionId != null) {
|
||||
return "searchCollection";
|
||||
}
|
||||
if (vaultFilter.selectedOrganizationId != null) {
|
||||
return "searchOrganization";
|
||||
}
|
||||
if (vaultFilter.myVaultOnly) {
|
||||
return "searchMyVault";
|
||||
}
|
||||
|
||||
return "searchVault";
|
||||
}
|
||||
}
|
@ -5,14 +5,15 @@ import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationGuardService implements CanActivate {
|
||||
export class PermissionsGuard implements CanActivate {
|
||||
constructor(
|
||||
private router: Router,
|
||||
private organizationService: OrganizationService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private organizationService: OrganizationService,
|
||||
private syncService: SyncService
|
||||
) {}
|
||||
|
||||
@ -26,6 +27,7 @@ export class OrganizationGuardService implements CanActivate {
|
||||
if (org == null) {
|
||||
return this.router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
if (!org.isOwner && !org.enabled) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
@ -35,6 +37,12 @@ export class OrganizationGuardService implements CanActivate {
|
||||
return this.router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
const permissions = route.data == null ? [] : (route.data.permissions as Permissions[]);
|
||||
if (permissions != null && !org.hasAnyPermission(permissions)) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied"));
|
||||
return this.router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<div class="org-nav" *ngIf="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>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="vault" routerLinkActive="active">
|
||||
<i class="bwi bwi-lock" aria-hidden="true"></i>
|
||||
{{ "vault" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="showManageTab">
|
||||
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
|
||||
<i class="bwi bwi-sliders" aria-hidden="true"></i>
|
||||
{{ "manage" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="showToolsTab">
|
||||
<a class="nav-link" [routerLink]="toolsRoute" routerLinkActive="active">
|
||||
<i class="bwi bwi-wrench" aria-hidden="true"></i>
|
||||
{{ "tools" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="showSettingsTab">
|
||||
<a class="nav-link" routerLink="settings" routerLinkActive="active">
|
||||
<i class="bwi bwi-cogs" aria-hidden="true"></i>
|
||||
{{ "settings" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
@ -5,6 +5,8 @@ import { BroadcasterService } from "jslib-common/abstractions/broadcaster.servic
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
|
||||
import { NavigationPermissionsService } from "../services/navigation-permissions.service";
|
||||
|
||||
const BroadcasterSubscriptionId = "OrganizationLayoutComponent";
|
||||
|
||||
@Component({
|
||||
@ -25,7 +27,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
this.route.params.subscribe(async (params) => {
|
||||
this.route.params.subscribe(async (params: any) => {
|
||||
this.organizationId = params.organizationId;
|
||||
await this.load();
|
||||
});
|
||||
@ -48,23 +50,16 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
this.organization = await this.organizationService.get(this.organizationId);
|
||||
}
|
||||
|
||||
get showMenuBar() {
|
||||
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
|
||||
}
|
||||
|
||||
get showManageTab(): boolean {
|
||||
return (
|
||||
this.organization.canManageUsers ||
|
||||
this.organization.canViewAllCollections ||
|
||||
this.organization.canViewAssignedCollections ||
|
||||
this.organization.canManageGroups ||
|
||||
this.organization.canManagePolicies ||
|
||||
this.organization.canAccessEventLogs
|
||||
);
|
||||
return NavigationPermissionsService.canAccessManage(this.organization);
|
||||
}
|
||||
|
||||
get showToolsTab(): boolean {
|
||||
return this.organization.canAccessImportExport || this.organization.canAccessReports;
|
||||
return NavigationPermissionsService.canAccessTools(this.organization);
|
||||
}
|
||||
|
||||
get showSettingsTab(): boolean {
|
||||
return NavigationPermissionsService.canAccessSettings(this.organization);
|
||||
}
|
||||
|
||||
get toolsRoute(): string {
|
221
src/app/organizations/organization-routing.module.ts
Normal file
221
src/app/organizations/organization-routing.module.ts
Normal file
@ -0,0 +1,221 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { AuthGuard } from "jslib-angular/guards/auth.guard";
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
|
||||
import { PermissionsGuard } from "./guards/permissions.guard";
|
||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
||||
import { CollectionsComponent } from "./manage/collections.component";
|
||||
import { EventsComponent } from "./manage/events.component";
|
||||
import { GroupsComponent } from "./manage/groups.component";
|
||||
import { ManageComponent } from "./manage/manage.component";
|
||||
import { PeopleComponent } from "./manage/people.component";
|
||||
import { PoliciesComponent } from "./manage/policies.component";
|
||||
import { NavigationPermissionsService } from "./services/navigation-permissions.service";
|
||||
import { AccountComponent } from "./settings/account.component";
|
||||
import { OrganizationBillingComponent } from "./settings/organization-billing.component";
|
||||
import { OrganizationSubscriptionComponent } from "./settings/organization-subscription.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
|
||||
import { ExportComponent } from "./tools/export.component";
|
||||
import { ExposedPasswordsReportComponent } from "./tools/exposed-passwords-report.component";
|
||||
import { ImportComponent } from "./tools/import.component";
|
||||
import { InactiveTwoFactorReportComponent } from "./tools/inactive-two-factor-report.component";
|
||||
import { ReusedPasswordsReportComponent } from "./tools/reused-passwords-report.component";
|
||||
import { ToolsComponent } from "./tools/tools.component";
|
||||
import { UnsecuredWebsitesReportComponent } from "./tools/unsecured-websites-report.component";
|
||||
import { WeakPasswordsReportComponent } from "./tools/weak-passwords-report.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ":organizationId",
|
||||
component: OrganizationLayoutComponent,
|
||||
canActivate: [AuthGuard, PermissionsGuard],
|
||||
data: {
|
||||
permissions: NavigationPermissionsService.getPermissions("admin"),
|
||||
},
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "vault" },
|
||||
{
|
||||
path: "vault",
|
||||
loadChildren: async () =>
|
||||
(await import("../modules/vault/modules/organization-vault/organization-vault.module"))
|
||||
.OrganizationVaultModule,
|
||||
},
|
||||
{
|
||||
path: "tools",
|
||||
component: ToolsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: { permissions: NavigationPermissionsService.getPermissions("tools") },
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
redirectTo: "import",
|
||||
},
|
||||
{
|
||||
path: "import",
|
||||
component: ImportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "importData",
|
||||
permissions: [Permissions.AccessImportExport],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "export",
|
||||
component: ExportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "exportVault",
|
||||
permissions: [Permissions.AccessImportExport],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "exposed-passwords-report",
|
||||
component: ExposedPasswordsReportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "exposedPasswordsReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "inactive-two-factor-report",
|
||||
component: InactiveTwoFactorReportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "inactive2faReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "reused-passwords-report",
|
||||
component: ReusedPasswordsReportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "reusedPasswordsReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "unsecured-websites-report",
|
||||
component: UnsecuredWebsitesReportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "unsecuredWebsitesReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "weak-passwords-report",
|
||||
component: WeakPasswordsReportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "weakPasswordsReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "manage",
|
||||
component: ManageComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
permissions: NavigationPermissionsService.getPermissions("manage"),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
redirectTo: "people",
|
||||
},
|
||||
{
|
||||
path: "collections",
|
||||
component: CollectionsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "collections",
|
||||
permissions: [
|
||||
Permissions.CreateNewCollections,
|
||||
Permissions.EditAnyCollection,
|
||||
Permissions.DeleteAnyCollection,
|
||||
Permissions.EditAssignedCollections,
|
||||
Permissions.DeleteAssignedCollections,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "events",
|
||||
component: EventsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "eventLogs",
|
||||
permissions: [Permissions.AccessEventLogs],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "groups",
|
||||
component: GroupsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "groups",
|
||||
permissions: [Permissions.ManageGroups],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "people",
|
||||
component: PeopleComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "people",
|
||||
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "policies",
|
||||
component: PoliciesComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
titleId: "policies",
|
||||
permissions: [Permissions.ManagePolicies],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "settings",
|
||||
component: SettingsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: { permissions: NavigationPermissionsService.getPermissions("settings") },
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "account" },
|
||||
{ path: "account", component: AccountComponent, data: { titleId: "myOrganization" } },
|
||||
{
|
||||
path: "two-factor",
|
||||
component: TwoFactorSetupComponent,
|
||||
data: { titleId: "twoStepLogin" },
|
||||
},
|
||||
{
|
||||
path: "billing",
|
||||
component: OrganizationBillingComponent,
|
||||
data: { titleId: "billing" },
|
||||
},
|
||||
{
|
||||
path: "subscription",
|
||||
component: OrganizationSubscriptionComponent,
|
||||
data: { titleId: "subscription" },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class OrganizationsRoutingModule {}
|
@ -0,0 +1,48 @@
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
|
||||
const permissions = {
|
||||
manage: [
|
||||
Permissions.CreateNewCollections,
|
||||
Permissions.EditAnyCollection,
|
||||
Permissions.DeleteAnyCollection,
|
||||
Permissions.EditAssignedCollections,
|
||||
Permissions.DeleteAssignedCollections,
|
||||
Permissions.AccessEventLogs,
|
||||
Permissions.ManageGroups,
|
||||
Permissions.ManageUsers,
|
||||
Permissions.ManagePolicies,
|
||||
],
|
||||
tools: [Permissions.AccessImportExport, Permissions.AccessReports],
|
||||
settings: [Permissions.ManageOrganization],
|
||||
};
|
||||
|
||||
export class NavigationPermissionsService {
|
||||
static getPermissions(route: keyof typeof permissions | "admin") {
|
||||
if (route === "admin") {
|
||||
return Object.values(permissions).reduce((previous, current) => previous.concat(current), []);
|
||||
}
|
||||
|
||||
return permissions[route];
|
||||
}
|
||||
|
||||
static canAccessAdmin(organization: Organization): boolean {
|
||||
return (
|
||||
this.canAccessTools(organization) ||
|
||||
this.canAccessSettings(organization) ||
|
||||
this.canAccessManage(organization)
|
||||
);
|
||||
}
|
||||
|
||||
static canAccessTools(organization: Organization): boolean {
|
||||
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("tools"));
|
||||
}
|
||||
|
||||
static canAccessSettings(organization: Organization): boolean {
|
||||
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("settings"));
|
||||
}
|
||||
|
||||
static canAccessManage(organization: Organization): boolean {
|
||||
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("manage"));
|
||||
}
|
||||
}
|
@ -5,23 +5,38 @@ import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
import { UserBillingComponent } from "../../settings/user-billing.component";
|
||||
import { PaymentMethodType } from "jslib-common/enums/paymentMethodType";
|
||||
import { TransactionType } from "jslib-common/enums/transactionType";
|
||||
import { VerifyBankRequest } from "jslib-common/models/request/verifyBankRequest";
|
||||
import { BillingResponse } from "jslib-common/models/response/billingResponse";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-billing",
|
||||
templateUrl: "../../settings/user-billing.component.html",
|
||||
templateUrl: "./organization-billing.component.html",
|
||||
})
|
||||
export class OrganizationBillingComponent extends UserBillingComponent implements OnInit {
|
||||
export class OrganizationBillingComponent implements OnInit {
|
||||
loading = false;
|
||||
firstLoaded = false;
|
||||
showAdjustPayment = false;
|
||||
showAddCredit = false;
|
||||
billing: BillingResponse;
|
||||
paymentMethodType = PaymentMethodType;
|
||||
transactionType = TransactionType;
|
||||
organizationId: string;
|
||||
verifyAmount1: number;
|
||||
verifyAmount2: number;
|
||||
|
||||
verifyBankPromise: Promise<any>;
|
||||
|
||||
// TODO - Make sure to properly split out the billing/invoice and payment method/account during org admin refresh
|
||||
|
||||
constructor(
|
||||
apiService: ApiService,
|
||||
i18nService: I18nService,
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
private route: ActivatedRoute,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(apiService, i18nService, platformUtilsService, logService);
|
||||
}
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
@ -30,4 +45,110 @@ export class OrganizationBillingComponent extends UserBillingComponent implement
|
||||
this.firstLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
if (this.organizationId != null) {
|
||||
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async verifyBank() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const request = new VerifyBankRequest();
|
||||
request.amount1 = this.verifyAmount1;
|
||||
request.amount2 = this.verifyAmount2;
|
||||
this.verifyBankPromise = this.apiService.postOrganizationVerifyBank(
|
||||
this.organizationId,
|
||||
request
|
||||
);
|
||||
await this.verifyBankPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("verifiedBankAccount")
|
||||
);
|
||||
this.load();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
addCredit() {
|
||||
if (this.paymentSourceInApp) {
|
||||
this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("cannotPerformInAppPurchase"),
|
||||
this.i18nService.t("addCredit"),
|
||||
null,
|
||||
null,
|
||||
"warning"
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.showAddCredit = true;
|
||||
}
|
||||
|
||||
closeAddCredit(load: boolean) {
|
||||
this.showAddCredit = false;
|
||||
if (load) {
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
|
||||
changePayment() {
|
||||
if (this.paymentSourceInApp) {
|
||||
this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("cannotPerformInAppPurchase"),
|
||||
this.i18nService.t("changePaymentMethod"),
|
||||
null,
|
||||
null,
|
||||
"warning"
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.showAdjustPayment = true;
|
||||
}
|
||||
|
||||
closePayment(load: boolean) {
|
||||
this.showAdjustPayment = false;
|
||||
if (load) {
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
|
||||
get isCreditBalance() {
|
||||
return this.billing == null || this.billing.balance <= 0;
|
||||
}
|
||||
|
||||
get creditOrBalance() {
|
||||
return Math.abs(this.billing != null ? this.billing.balance : 0);
|
||||
}
|
||||
|
||||
get paymentSource() {
|
||||
return this.billing != null ? this.billing.paymentSource : null;
|
||||
}
|
||||
|
||||
get paymentSourceInApp() {
|
||||
return (
|
||||
this.paymentSource != null &&
|
||||
(this.paymentSource.type === PaymentMethodType.AppleInApp ||
|
||||
this.paymentSource.type === PaymentMethodType.GoogleInApp)
|
||||
);
|
||||
}
|
||||
|
||||
get invoices() {
|
||||
return this.billing != null ? this.billing.invoices : null;
|
||||
}
|
||||
|
||||
get transactions() {
|
||||
return this.billing != null ? this.billing.transactions : null;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { EventService } from "jslib-common/abstractions/event.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||
@ -37,7 +38,8 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
totpService: TotpService,
|
||||
passwordRepromptService: PasswordRepromptService,
|
||||
logService: LogService,
|
||||
stateService: StateService
|
||||
stateService: StateService,
|
||||
organizationService: OrganizationService
|
||||
) {
|
||||
super(
|
||||
searchService,
|
||||
@ -48,7 +50,8 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
totpService,
|
||||
stateService,
|
||||
passwordRepromptService,
|
||||
logService
|
||||
logService,
|
||||
organizationService
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,65 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { CollectionData } from "jslib-common/models/data/collectionData";
|
||||
import { Collection } from "jslib-common/models/domain/collection";
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
import { CollectionDetailsResponse } from "jslib-common/models/response/collectionResponse";
|
||||
import { CollectionView } from "jslib-common/models/view/collectionView";
|
||||
|
||||
import { GroupingsComponent as BaseGroupingsComponent } from "../../vault/groupings.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-vault-groupings",
|
||||
templateUrl: "../../vault/groupings.component.html",
|
||||
})
|
||||
export class GroupingsComponent extends BaseGroupingsComponent {
|
||||
organization: Organization;
|
||||
|
||||
constructor(
|
||||
collectionService: CollectionService,
|
||||
folderService: FolderService,
|
||||
stateService: StateService,
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService
|
||||
) {
|
||||
super(collectionService, folderService, stateService);
|
||||
}
|
||||
|
||||
async loadCollections() {
|
||||
if (!this.organization.canEditAnyCollection) {
|
||||
await super.loadCollections(this.organization.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const collections = await this.apiService.getCollections(this.organization.id);
|
||||
if (collections != null && collections.data != null && collections.data.length) {
|
||||
const collectionDomains = collections.data.map(
|
||||
(r) => new Collection(new CollectionData(r as CollectionDetailsResponse))
|
||||
);
|
||||
this.collections = await this.collectionService.decryptMany(collectionDomains);
|
||||
} else {
|
||||
this.collections = [];
|
||||
}
|
||||
|
||||
const unassignedCollection = new CollectionView();
|
||||
unassignedCollection.name = this.i18nService.t("unassigned");
|
||||
unassignedCollection.id = "unassigned";
|
||||
unassignedCollection.organizationId = this.organization.id;
|
||||
unassignedCollection.readOnly = true;
|
||||
this.collections.push(unassignedCollection);
|
||||
this.nestedCollections = await this.collectionService.getAllNested(this.collections);
|
||||
}
|
||||
|
||||
async collapse(grouping: CollectionView) {
|
||||
await super.collapse(grouping, "org_");
|
||||
}
|
||||
|
||||
isCollapsed(grouping: CollectionView) {
|
||||
return super.isCollapsed(grouping, "org_");
|
||||
}
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
||||
import { LockGuardService } from "jslib-angular/services/lock-guard.service";
|
||||
import { UnauthGuardService } from "jslib-angular/services/unauth-guard.service";
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
import { AuthGuard } from "jslib-angular/guards/auth.guard";
|
||||
import { LockGuard } from "jslib-angular/guards/lock.guard";
|
||||
import { UnauthGuard } from "jslib-angular/guards/unauth.guard";
|
||||
|
||||
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
|
||||
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
|
||||
@ -24,52 +23,23 @@ import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.compone
|
||||
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
|
||||
import { HomeGuard } from "./guards/home.guard";
|
||||
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
|
||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
||||
import { UserLayoutComponent } from "./layouts/user-layout.component";
|
||||
import { CollectionsComponent as OrgManageCollectionsComponent } from "./organizations/manage/collections.component";
|
||||
import { EventsComponent as OrgEventsComponent } from "./organizations/manage/events.component";
|
||||
import { GroupsComponent as OrgGroupsComponent } from "./organizations/manage/groups.component";
|
||||
import { ManageComponent as OrgManageComponent } from "./organizations/manage/manage.component";
|
||||
import { PeopleComponent as OrgPeopleComponent } from "./organizations/manage/people.component";
|
||||
import { PoliciesComponent as OrgPoliciesComponent } from "./organizations/manage/policies.component";
|
||||
import { AccountComponent as OrgAccountComponent } from "./organizations/settings/account.component";
|
||||
import { OrganizationBillingComponent } from "./organizations/settings/organization-billing.component";
|
||||
import { OrganizationSubscriptionComponent } from "./organizations/settings/organization-subscription.component";
|
||||
import { SettingsComponent as OrgSettingsComponent } from "./organizations/settings/settings.component";
|
||||
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "./organizations/settings/two-factor-setup.component";
|
||||
import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component";
|
||||
import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component";
|
||||
import { ExportComponent as OrgExportComponent } from "./organizations/tools/export.component";
|
||||
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "./organizations/tools/exposed-passwords-report.component";
|
||||
import { ImportComponent as OrgImportComponent } from "./organizations/tools/import.component";
|
||||
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "./organizations/tools/inactive-two-factor-report.component";
|
||||
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "./organizations/tools/reused-passwords-report.component";
|
||||
import { ToolsComponent as OrgToolsComponent } from "./organizations/tools/tools.component";
|
||||
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "./organizations/tools/unsecured-websites-report.component";
|
||||
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "./organizations/tools/weak-passwords-report.component";
|
||||
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
|
||||
import { AccessComponent } from "./send/access.component";
|
||||
import { SendComponent } from "./send/send.component";
|
||||
import { OrganizationGuardService } from "./services/organization-guard.service";
|
||||
import { OrganizationTypeGuardService } from "./services/organization-type-guard.service";
|
||||
import { AccountComponent } from "./settings/account.component";
|
||||
import { CreateOrganizationComponent } from "./settings/create-organization.component";
|
||||
import { DomainRulesComponent } from "./settings/domain-rules.component";
|
||||
import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.component";
|
||||
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
|
||||
import { OptionsComponent } from "./settings/options.component";
|
||||
import { OrganizationsComponent } from "./settings/organizations.component";
|
||||
import { PremiumComponent } from "./settings/premium.component";
|
||||
import { PreferencesComponent } from "./settings/preferences.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
|
||||
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
|
||||
import { UserBillingComponent } from "./settings/user-billing.component";
|
||||
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
|
||||
import { ExportComponent } from "./tools/export.component";
|
||||
import { GeneratorComponent } from "./tools/generator.component";
|
||||
import { ImportComponent } from "./tools/import.component";
|
||||
import { ToolsComponent } from "./tools/tools.component";
|
||||
import { VaultComponent } from "./vault/vault.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -83,18 +53,18 @@ const routes: Routes = [
|
||||
children: [], // Children lets us have an empty component.
|
||||
canActivate: [HomeGuard], // Redirects either to vault, login or lock page.
|
||||
},
|
||||
{ path: "login", component: LoginComponent, canActivate: [UnauthGuardService] },
|
||||
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuardService] },
|
||||
{ path: "login", component: LoginComponent, canActivate: [UnauthGuard] },
|
||||
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
|
||||
{
|
||||
path: "register",
|
||||
component: RegisterComponent,
|
||||
canActivate: [UnauthGuardService],
|
||||
canActivate: [UnauthGuard],
|
||||
data: { titleId: "createAccount" },
|
||||
},
|
||||
{
|
||||
path: "sso",
|
||||
component: SsoComponent,
|
||||
canActivate: [UnauthGuardService],
|
||||
canActivate: [UnauthGuard],
|
||||
data: { titleId: "enterpriseSingleSignOn" },
|
||||
},
|
||||
{
|
||||
@ -105,13 +75,13 @@ const routes: Routes = [
|
||||
{
|
||||
path: "hint",
|
||||
component: HintComponent,
|
||||
canActivate: [UnauthGuardService],
|
||||
canActivate: [UnauthGuard],
|
||||
data: { titleId: "passwordHint" },
|
||||
},
|
||||
{
|
||||
path: "lock",
|
||||
component: LockComponent,
|
||||
canActivate: [LockGuardService],
|
||||
canActivate: [LockGuard],
|
||||
},
|
||||
{ path: "verify-email", component: VerifyEmailTokenComponent },
|
||||
{
|
||||
@ -133,19 +103,19 @@ const routes: Routes = [
|
||||
{
|
||||
path: "recover-2fa",
|
||||
component: RecoverTwoFactorComponent,
|
||||
canActivate: [UnauthGuardService],
|
||||
canActivate: [UnauthGuard],
|
||||
data: { titleId: "recoverAccountTwoStep" },
|
||||
},
|
||||
{
|
||||
path: "recover-delete",
|
||||
component: RecoverDeleteComponent,
|
||||
canActivate: [UnauthGuardService],
|
||||
canActivate: [UnauthGuard],
|
||||
data: { titleId: "deleteAccount" },
|
||||
},
|
||||
{
|
||||
path: "verify-recover-delete",
|
||||
component: VerifyRecoverDeleteComponent,
|
||||
canActivate: [UnauthGuardService],
|
||||
canActivate: [UnauthGuard],
|
||||
data: { titleId: "deleteAccount" },
|
||||
},
|
||||
{
|
||||
@ -156,19 +126,19 @@ const routes: Routes = [
|
||||
{
|
||||
path: "update-temp-password",
|
||||
component: UpdateTempPasswordComponent,
|
||||
canActivate: [AuthGuardService],
|
||||
canActivate: [AuthGuard],
|
||||
data: { titleId: "updateTempPassword" },
|
||||
},
|
||||
{
|
||||
path: "update-password",
|
||||
component: UpdatePasswordComponent,
|
||||
canActivate: [AuthGuardService],
|
||||
canActivate: [AuthGuard],
|
||||
data: { titleId: "updatePassword" },
|
||||
},
|
||||
{
|
||||
path: "remove-password",
|
||||
component: RemovePasswordComponent,
|
||||
canActivate: [AuthGuardService],
|
||||
canActivate: [AuthGuard],
|
||||
data: { titleId: "removeMasterPassword" },
|
||||
},
|
||||
],
|
||||
@ -176,9 +146,14 @@ const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: UserLayoutComponent,
|
||||
canActivate: [AuthGuardService],
|
||||
canActivate: [AuthGuard],
|
||||
children: [
|
||||
{ path: "vault", component: VaultComponent, data: { titleId: "myVault" } },
|
||||
{
|
||||
path: "vault",
|
||||
loadChildren: async () =>
|
||||
(await import("./modules/vault/modules/individual-vault/individual-vault.module"))
|
||||
.IndividualVaultModule,
|
||||
},
|
||||
{ path: "sends", component: SendComponent, data: { title: "Send" } },
|
||||
{
|
||||
path: "settings",
|
||||
@ -186,28 +161,25 @@ const routes: Routes = [
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "account" },
|
||||
{ path: "account", component: AccountComponent, data: { titleId: "myAccount" } },
|
||||
{ path: "options", component: OptionsComponent, data: { titleId: "options" } },
|
||||
{
|
||||
path: "preferences",
|
||||
component: PreferencesComponent,
|
||||
data: { titleId: "preferences" },
|
||||
},
|
||||
{
|
||||
path: "security",
|
||||
loadChildren: async () =>
|
||||
(await import("./settings/security-routing.module")).SecurityRoutingModule,
|
||||
},
|
||||
{
|
||||
path: "domain-rules",
|
||||
component: DomainRulesComponent,
|
||||
data: { titleId: "domainRules" },
|
||||
},
|
||||
{
|
||||
path: "two-factor",
|
||||
component: TwoFactorSetupComponent,
|
||||
data: { titleId: "twoStepLogin" },
|
||||
},
|
||||
{ path: "premium", component: PremiumComponent, data: { titleId: "goPremium" } },
|
||||
{ path: "billing", component: UserBillingComponent, data: { titleId: "billing" } },
|
||||
{
|
||||
path: "subscription",
|
||||
component: UserSubscriptionComponent,
|
||||
data: { titleId: "premiumMembership" },
|
||||
},
|
||||
{
|
||||
path: "organizations",
|
||||
component: OrganizationsComponent,
|
||||
data: { titleId: "organizations" },
|
||||
loadChildren: async () =>
|
||||
(await import("./settings/subscription-routing.module")).SubscriptionRoutingModule,
|
||||
},
|
||||
{
|
||||
path: "create-organization",
|
||||
@ -239,7 +211,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: "tools",
|
||||
component: ToolsComponent,
|
||||
canActivate: [AuthGuardService],
|
||||
canActivate: [AuthGuard],
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "generator" },
|
||||
{ path: "import", component: ImportComponent, data: { titleId: "importData" } },
|
||||
@ -253,194 +225,16 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: "reports",
|
||||
loadChildren: async () => (await import("./reports/reports.module")).ReportsModule,
|
||||
loadChildren: async () =>
|
||||
(await import("./reports/reports-routing.module")).ReportsRoutingModule,
|
||||
},
|
||||
{ path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "organizations/:organizationId",
|
||||
component: OrganizationLayoutComponent,
|
||||
canActivate: [AuthGuardService, OrganizationGuardService],
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "vault" },
|
||||
{ path: "vault", component: OrgVaultComponent, data: { titleId: "vault" } },
|
||||
{
|
||||
path: "tools",
|
||||
component: OrgToolsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: { permissions: [Permissions.AccessImportExport, Permissions.AccessReports] },
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
redirectTo: "import",
|
||||
},
|
||||
{
|
||||
path: "import",
|
||||
component: OrgImportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "importData",
|
||||
permissions: [Permissions.AccessImportExport],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "export",
|
||||
component: OrgExportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "exportVault",
|
||||
permissions: [Permissions.AccessImportExport],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "exposed-passwords-report",
|
||||
component: OrgExposedPasswordsReportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "exposedPasswordsReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "inactive-two-factor-report",
|
||||
component: OrgInactiveTwoFactorReportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "inactive2faReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "reused-passwords-report",
|
||||
component: OrgReusedPasswordsReportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "reusedPasswordsReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "unsecured-websites-report",
|
||||
component: OrgUnsecuredWebsitesReportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "unsecuredWebsitesReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "weak-passwords-report",
|
||||
component: OrgWeakPasswordsReportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "weakPasswordsReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "manage",
|
||||
component: OrgManageComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
permissions: [
|
||||
Permissions.CreateNewCollections,
|
||||
Permissions.EditAnyCollection,
|
||||
Permissions.DeleteAnyCollection,
|
||||
Permissions.EditAssignedCollections,
|
||||
Permissions.DeleteAssignedCollections,
|
||||
Permissions.AccessEventLogs,
|
||||
Permissions.ManageGroups,
|
||||
Permissions.ManageUsers,
|
||||
Permissions.ManagePolicies,
|
||||
],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
redirectTo: "people",
|
||||
},
|
||||
{
|
||||
path: "collections",
|
||||
component: OrgManageCollectionsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "collections",
|
||||
permissions: [
|
||||
Permissions.CreateNewCollections,
|
||||
Permissions.EditAnyCollection,
|
||||
Permissions.DeleteAnyCollection,
|
||||
Permissions.EditAssignedCollections,
|
||||
Permissions.DeleteAssignedCollections,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "events",
|
||||
component: OrgEventsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "eventLogs",
|
||||
permissions: [Permissions.AccessEventLogs],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "groups",
|
||||
component: OrgGroupsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "groups",
|
||||
permissions: [Permissions.ManageGroups],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "people",
|
||||
component: OrgPeopleComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "people",
|
||||
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "policies",
|
||||
component: OrgPoliciesComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: "policies",
|
||||
permissions: [Permissions.ManagePolicies],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "settings",
|
||||
component: OrgSettingsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: { permissions: [Permissions.ManageOrganization] },
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "account" },
|
||||
{ path: "account", component: OrgAccountComponent, data: { titleId: "myOrganization" } },
|
||||
{
|
||||
path: "two-factor",
|
||||
component: OrgTwoFactorSetupComponent,
|
||||
data: { titleId: "twoStepLogin" },
|
||||
},
|
||||
{
|
||||
path: "billing",
|
||||
component: OrganizationBillingComponent,
|
||||
data: { titleId: "billing" },
|
||||
},
|
||||
{
|
||||
path: "subscription",
|
||||
component: OrganizationSubscriptionComponent,
|
||||
data: { titleId: "subscription" },
|
||||
},
|
||||
],
|
||||
path: "organizations",
|
||||
loadChildren: () =>
|
||||
import("./organizations/organization-routing.module").then(
|
||||
(m) => m.OrganizationsRoutingModule
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -451,7 +245,7 @@ const routes: Routes = [
|
||||
RouterModule.forRoot(routes, {
|
||||
useHash: true,
|
||||
paramsInheritanceStrategy: "always",
|
||||
/*enableTracing: true,*/
|
||||
// enableTracing: true,
|
||||
}),
|
||||
],
|
||||
exports: [RouterModule],
|
||||
|
@ -1,448 +1,20 @@
|
||||
import { DragDropModule } from "@angular/cdk/drag-drop";
|
||||
import { DatePipe, registerLocaleData, CommonModule } from "@angular/common";
|
||||
import localeAf from "@angular/common/locales/af";
|
||||
import localeAz from "@angular/common/locales/az";
|
||||
import localeBe from "@angular/common/locales/be";
|
||||
import localeBg from "@angular/common/locales/bg";
|
||||
import localeBn from "@angular/common/locales/bn";
|
||||
import localeBs from "@angular/common/locales/bs";
|
||||
import localeCa from "@angular/common/locales/ca";
|
||||
import localeCs from "@angular/common/locales/cs";
|
||||
import localeDa from "@angular/common/locales/da";
|
||||
import localeDe from "@angular/common/locales/de";
|
||||
import localeEl from "@angular/common/locales/el";
|
||||
import localeEnGb from "@angular/common/locales/en-GB";
|
||||
import localeEnIn from "@angular/common/locales/en-IN";
|
||||
import localeEo from "@angular/common/locales/eo";
|
||||
import localeEs from "@angular/common/locales/es";
|
||||
import localeEt from "@angular/common/locales/et";
|
||||
import localeFi from "@angular/common/locales/fi";
|
||||
import localeFil from "@angular/common/locales/fil";
|
||||
import localeFr from "@angular/common/locales/fr";
|
||||
import localeHe from "@angular/common/locales/he";
|
||||
import localeHi from "@angular/common/locales/hi";
|
||||
import localeHr from "@angular/common/locales/hr";
|
||||
import localeHu from "@angular/common/locales/hu";
|
||||
import localeId from "@angular/common/locales/id";
|
||||
import localeIt from "@angular/common/locales/it";
|
||||
import localeJa from "@angular/common/locales/ja";
|
||||
import localeKa from "@angular/common/locales/ka";
|
||||
import localeKm from "@angular/common/locales/km";
|
||||
import localeKn from "@angular/common/locales/kn";
|
||||
import localeKo from "@angular/common/locales/ko";
|
||||
import localeLv from "@angular/common/locales/lv";
|
||||
import localeMl from "@angular/common/locales/ml";
|
||||
import localeNb from "@angular/common/locales/nb";
|
||||
import localeNl from "@angular/common/locales/nl";
|
||||
import localeNn from "@angular/common/locales/nn";
|
||||
import localePl from "@angular/common/locales/pl";
|
||||
import localePtBr from "@angular/common/locales/pt";
|
||||
import localePtPt from "@angular/common/locales/pt-PT";
|
||||
import localeRo from "@angular/common/locales/ro";
|
||||
import localeRu from "@angular/common/locales/ru";
|
||||
import localeSi from "@angular/common/locales/si";
|
||||
import localeSk from "@angular/common/locales/sk";
|
||||
import localeSl from "@angular/common/locales/sl";
|
||||
import localeSr from "@angular/common/locales/sr";
|
||||
import localeSv from "@angular/common/locales/sv";
|
||||
import localeTr from "@angular/common/locales/tr";
|
||||
import localeUk from "@angular/common/locales/uk";
|
||||
import localeVi from "@angular/common/locales/vi";
|
||||
import localeZhCn from "@angular/common/locales/zh-Hans";
|
||||
import localeZhTw from "@angular/common/locales/zh-Hant";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { BadgeModule, ButtonModule } from "@bitwarden/components";
|
||||
import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
||||
import { ToastrModule } from "ngx-toastr";
|
||||
|
||||
import { UserVerificationComponent } from "jslib-angular/components/user-verification.component";
|
||||
import { JslibModule } from "jslib-angular/jslib.module";
|
||||
|
||||
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
|
||||
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
|
||||
import { HintComponent } from "./accounts/hint.component";
|
||||
import { LockComponent } from "./accounts/lock.component";
|
||||
import { LoginComponent } from "./accounts/login.component";
|
||||
import { RecoverDeleteComponent } from "./accounts/recover-delete.component";
|
||||
import { RecoverTwoFactorComponent } from "./accounts/recover-two-factor.component";
|
||||
import { RegisterComponent } from "./accounts/register.component";
|
||||
import { RemovePasswordComponent } from "./accounts/remove-password.component";
|
||||
import { SetPasswordComponent } from "./accounts/set-password.component";
|
||||
import { SsoComponent } from "./accounts/sso.component";
|
||||
import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
|
||||
import { TwoFactorComponent } from "./accounts/two-factor.component";
|
||||
import { UpdatePasswordComponent } from "./accounts/update-password.component";
|
||||
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
|
||||
import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component";
|
||||
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
|
||||
import { NestedCheckboxComponent } from "./components/nested-checkbox.component";
|
||||
import { PasswordRepromptComponent } from "./components/password-reprompt.component";
|
||||
import { PasswordStrengthComponent } from "./components/password-strength.component";
|
||||
import { PremiumBadgeComponent } from "./components/premium-badge.component";
|
||||
import { FooterComponent } from "./layouts/footer.component";
|
||||
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
|
||||
import { NavbarComponent } from "./layouts/navbar.component";
|
||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
||||
import { UserLayoutComponent } from "./layouts/user-layout.component";
|
||||
import { BulkConfirmComponent as OrgBulkConfirmComponent } from "./organizations/manage/bulk/bulk-confirm.component";
|
||||
import { BulkRemoveComponent as OrgBulkRemoveComponent } from "./organizations/manage/bulk/bulk-remove.component";
|
||||
import { BulkStatusComponent as OrgBulkStatusComponent } from "./organizations/manage/bulk/bulk-status.component";
|
||||
import { CollectionAddEditComponent as OrgCollectionAddEditComponent } from "./organizations/manage/collection-add-edit.component";
|
||||
import { CollectionsComponent as OrgManageCollectionsComponent } from "./organizations/manage/collections.component";
|
||||
import { EntityEventsComponent as OrgEntityEventsComponent } from "./organizations/manage/entity-events.component";
|
||||
import { EntityUsersComponent as OrgEntityUsersComponent } from "./organizations/manage/entity-users.component";
|
||||
import { EventsComponent as OrgEventsComponent } from "./organizations/manage/events.component";
|
||||
import { GroupAddEditComponent as OrgGroupAddEditComponent } from "./organizations/manage/group-add-edit.component";
|
||||
import { GroupsComponent as OrgGroupsComponent } from "./organizations/manage/groups.component";
|
||||
import { ManageComponent as OrgManageComponent } from "./organizations/manage/manage.component";
|
||||
import { PeopleComponent as OrgPeopleComponent } from "./organizations/manage/people.component";
|
||||
import { PoliciesComponent as OrgPoliciesComponent } from "./organizations/manage/policies.component";
|
||||
import { PolicyEditComponent as OrgPolicyEditComponent } from "./organizations/manage/policy-edit.component";
|
||||
import { ResetPasswordComponent as OrgResetPasswordComponent } from "./organizations/manage/reset-password.component";
|
||||
import { UserAddEditComponent as OrgUserAddEditComponent } from "./organizations/manage/user-add-edit.component";
|
||||
import { UserConfirmComponent as OrgUserConfirmComponent } from "./organizations/manage/user-confirm.component";
|
||||
import { UserGroupsComponent as OrgUserGroupsComponent } from "./organizations/manage/user-groups.component";
|
||||
import { DisableSendPolicyComponent } from "./organizations/policies/disable-send.component";
|
||||
import { MasterPasswordPolicyComponent } from "./organizations/policies/master-password.component";
|
||||
import { PasswordGeneratorPolicyComponent } from "./organizations/policies/password-generator.component";
|
||||
import { PersonalOwnershipPolicyComponent } from "./organizations/policies/personal-ownership.component";
|
||||
import { RequireSsoPolicyComponent } from "./organizations/policies/require-sso.component";
|
||||
import { ResetPasswordPolicyComponent } from "./organizations/policies/reset-password.component";
|
||||
import { SendOptionsPolicyComponent } from "./organizations/policies/send-options.component";
|
||||
import { SingleOrgPolicyComponent } from "./organizations/policies/single-org.component";
|
||||
import { TwoFactorAuthenticationPolicyComponent } from "./organizations/policies/two-factor-authentication.component";
|
||||
import { AccountComponent as OrgAccountComponent } from "./organizations/settings/account.component";
|
||||
import { AdjustSubscription } from "./organizations/settings/adjust-subscription.component";
|
||||
import { ChangePlanComponent } from "./organizations/settings/change-plan.component";
|
||||
import { DeleteOrganizationComponent } from "./organizations/settings/delete-organization.component";
|
||||
import { DownloadLicenseComponent } from "./organizations/settings/download-license.component";
|
||||
import { OrganizationBillingComponent } from "./organizations/settings/organization-billing.component";
|
||||
import { OrganizationSubscriptionComponent } from "./organizations/settings/organization-subscription.component";
|
||||
import { SettingsComponent as OrgSettingComponent } from "./organizations/settings/settings.component";
|
||||
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "./organizations/settings/two-factor-setup.component";
|
||||
import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component";
|
||||
import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component";
|
||||
import { ExportComponent as OrgExportComponent } from "./organizations/tools/export.component";
|
||||
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "./organizations/tools/exposed-passwords-report.component";
|
||||
import { ImportComponent as OrgImportComponent } from "./organizations/tools/import.component";
|
||||
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "./organizations/tools/inactive-two-factor-report.component";
|
||||
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "./organizations/tools/reused-passwords-report.component";
|
||||
import { ToolsComponent as OrgToolsComponent } from "./organizations/tools/tools.component";
|
||||
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "./organizations/tools/unsecured-websites-report.component";
|
||||
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "./organizations/tools/weak-passwords-report.component";
|
||||
import { AddEditComponent as OrgAddEditComponent } from "./organizations/vault/add-edit.component";
|
||||
import { AttachmentsComponent as OrgAttachmentsComponent } from "./organizations/vault/attachments.component";
|
||||
import { CiphersComponent as OrgCiphersComponent } from "./organizations/vault/ciphers.component";
|
||||
import { CollectionsComponent as OrgCollectionsComponent } from "./organizations/vault/collections.component";
|
||||
import { GroupingsComponent as OrgGroupingsComponent } from "./organizations/vault/groupings.component";
|
||||
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
|
||||
import { ProvidersComponent } from "./providers/providers.component";
|
||||
import { BreachReportComponent } from "./reports/breach-report.component";
|
||||
import { ExposedPasswordsReportComponent } from "./reports/exposed-passwords-report.component";
|
||||
import { InactiveTwoFactorReportComponent } from "./reports/inactive-two-factor-report.component";
|
||||
import { ReportCardComponent } from "./reports/report-card.component";
|
||||
import { ReportListComponent } from "./reports/report-list.component";
|
||||
import { ReportsComponent } from "./reports/reports.component";
|
||||
import { ReusedPasswordsReportComponent } from "./reports/reused-passwords-report.component";
|
||||
import { UnsecuredWebsitesReportComponent } from "./reports/unsecured-websites-report.component";
|
||||
import { WeakPasswordsReportComponent } from "./reports/weak-passwords-report.component";
|
||||
import { AccessComponent } from "./send/access.component";
|
||||
import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
|
||||
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
|
||||
import { SendComponent } from "./send/send.component";
|
||||
import { AccountComponent } from "./settings/account.component";
|
||||
import { AddCreditComponent } from "./settings/add-credit.component";
|
||||
import { AdjustPaymentComponent } from "./settings/adjust-payment.component";
|
||||
import { AdjustStorageComponent } from "./settings/adjust-storage.component";
|
||||
import { ApiKeyComponent } from "./settings/api-key.component";
|
||||
import { ChangeEmailComponent } from "./settings/change-email.component";
|
||||
import { ChangeKdfComponent } from "./settings/change-kdf.component";
|
||||
import { ChangePasswordComponent } from "./settings/change-password.component";
|
||||
import { CreateOrganizationComponent } from "./settings/create-organization.component";
|
||||
import { DeauthorizeSessionsComponent } from "./settings/deauthorize-sessions.component";
|
||||
import { DeleteAccountComponent } from "./settings/delete-account.component";
|
||||
import { DomainRulesComponent } from "./settings/domain-rules.component";
|
||||
import { EmergencyAccessAddEditComponent } from "./settings/emergency-access-add-edit.component";
|
||||
import { EmergencyAccessAttachmentsComponent } from "./settings/emergency-access-attachments.component";
|
||||
import { EmergencyAccessConfirmComponent } from "./settings/emergency-access-confirm.component";
|
||||
import { EmergencyAccessTakeoverComponent } from "./settings/emergency-access-takeover.component";
|
||||
import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.component";
|
||||
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
|
||||
import { EmergencyAddEditComponent } from "./settings/emergency-add-edit.component";
|
||||
import { LinkSsoComponent } from "./settings/link-sso.component";
|
||||
import { OptionsComponent } from "./settings/options.component";
|
||||
import { OrganizationPlansComponent } from "./settings/organization-plans.component";
|
||||
import { OrganizationsComponent } from "./settings/organizations.component";
|
||||
import { PaymentComponent } from "./settings/payment.component";
|
||||
import { PremiumComponent } from "./settings/premium.component";
|
||||
import { ProfileComponent } from "./settings/profile.component";
|
||||
import { PurgeVaultComponent } from "./settings/purge-vault.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
|
||||
import { SponsoringOrgRowComponent } from "./settings/sponsoring-org-row.component";
|
||||
import { TaxInfoComponent } from "./settings/tax-info.component";
|
||||
import { TwoFactorAuthenticatorComponent } from "./settings/two-factor-authenticator.component";
|
||||
import { TwoFactorDuoComponent } from "./settings/two-factor-duo.component";
|
||||
import { TwoFactorEmailComponent } from "./settings/two-factor-email.component";
|
||||
import { TwoFactorRecoveryComponent } from "./settings/two-factor-recovery.component";
|
||||
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
|
||||
import { TwoFactorVerifyComponent } from "./settings/two-factor-verify.component";
|
||||
import { TwoFactorWebAuthnComponent } from "./settings/two-factor-webauthn.component";
|
||||
import { TwoFactorYubiKeyComponent } from "./settings/two-factor-yubikey.component";
|
||||
import { UpdateKeyComponent } from "./settings/update-key.component";
|
||||
import { UpdateLicenseComponent } from "./settings/update-license.component";
|
||||
import { UserBillingComponent } from "./settings/user-billing.component";
|
||||
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
|
||||
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
|
||||
import { VerifyEmailComponent } from "./settings/verify-email.component";
|
||||
import { ExportComponent } from "./tools/export.component";
|
||||
import { GeneratorComponent } from "./tools/generator.component";
|
||||
import { ImportComponent } from "./tools/import.component";
|
||||
import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component";
|
||||
import { ToolsComponent } from "./tools/tools.component";
|
||||
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
|
||||
import { AddEditComponent } from "./vault/add-edit.component";
|
||||
import { AttachmentsComponent } from "./vault/attachments.component";
|
||||
import { BulkActionsComponent } from "./vault/bulk-actions.component";
|
||||
import { BulkDeleteComponent } from "./vault/bulk-delete.component";
|
||||
import { BulkMoveComponent } from "./vault/bulk-move.component";
|
||||
import { BulkRestoreComponent } from "./vault/bulk-restore.component";
|
||||
import { BulkShareComponent } from "./vault/bulk-share.component";
|
||||
import { CiphersComponent } from "./vault/ciphers.component";
|
||||
import { CollectionsComponent } from "./vault/collections.component";
|
||||
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
|
||||
import { GroupingsComponent } from "./vault/groupings.component";
|
||||
import { ShareComponent } from "./vault/share.component";
|
||||
import { VaultComponent } from "./vault/vault.component";
|
||||
|
||||
registerLocaleData(localeAf, "af");
|
||||
registerLocaleData(localeAz, "az");
|
||||
registerLocaleData(localeBe, "be");
|
||||
registerLocaleData(localeBg, "bg");
|
||||
registerLocaleData(localeBn, "bn");
|
||||
registerLocaleData(localeBs, "bs");
|
||||
registerLocaleData(localeCa, "ca");
|
||||
registerLocaleData(localeCs, "cs");
|
||||
registerLocaleData(localeDa, "da");
|
||||
registerLocaleData(localeDe, "de");
|
||||
registerLocaleData(localeEl, "el");
|
||||
registerLocaleData(localeEnGb, "en-GB");
|
||||
registerLocaleData(localeEnIn, "en-IN");
|
||||
registerLocaleData(localeEo, "eo");
|
||||
registerLocaleData(localeEs, "es");
|
||||
registerLocaleData(localeEt, "et");
|
||||
registerLocaleData(localeFi, "fi");
|
||||
registerLocaleData(localeFil, "fil");
|
||||
registerLocaleData(localeFr, "fr");
|
||||
registerLocaleData(localeHe, "he");
|
||||
registerLocaleData(localeHi, "hi");
|
||||
registerLocaleData(localeHr, "hr");
|
||||
registerLocaleData(localeHu, "hu");
|
||||
registerLocaleData(localeId, "id");
|
||||
registerLocaleData(localeIt, "it");
|
||||
registerLocaleData(localeJa, "ja");
|
||||
registerLocaleData(localeKa, "ka");
|
||||
registerLocaleData(localeKm, "km");
|
||||
registerLocaleData(localeKn, "kn");
|
||||
registerLocaleData(localeKo, "ko");
|
||||
registerLocaleData(localeLv, "lv");
|
||||
registerLocaleData(localeMl, "ml");
|
||||
registerLocaleData(localeNb, "nb");
|
||||
registerLocaleData(localeNl, "nl");
|
||||
registerLocaleData(localeNn, "nn");
|
||||
registerLocaleData(localePl, "pl");
|
||||
registerLocaleData(localePtBr, "pt-BR");
|
||||
registerLocaleData(localePtPt, "pt-PT");
|
||||
registerLocaleData(localeRo, "ro");
|
||||
registerLocaleData(localeRu, "ru");
|
||||
registerLocaleData(localeSi, "si");
|
||||
registerLocaleData(localeSk, "sk");
|
||||
registerLocaleData(localeSl, "sl");
|
||||
registerLocaleData(localeSr, "sr");
|
||||
registerLocaleData(localeSv, "sv");
|
||||
registerLocaleData(localeTr, "tr");
|
||||
registerLocaleData(localeUk, "uk");
|
||||
registerLocaleData(localeVi, "vi");
|
||||
registerLocaleData(localeZhCn, "zh-CN");
|
||||
registerLocaleData(localeZhTw, "zh-TW");
|
||||
import { LooseComponentsModule } from "./modules/loose-components.module";
|
||||
import { PipesModule } from "./modules/pipes/pipes.module";
|
||||
import { SharedModule } from "./modules/shared.module";
|
||||
import { VaultFilterModule } from "./modules/vault-filter/vault-filter.module";
|
||||
import { OrganizationBadgeModule } from "./modules/vault/modules/organization-badge/organization-badge.module";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
DragDropModule,
|
||||
FormsModule,
|
||||
InfiniteScrollModule,
|
||||
JslibModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
ToastrModule,
|
||||
BadgeModule,
|
||||
ButtonModule,
|
||||
SharedModule,
|
||||
LooseComponentsModule,
|
||||
VaultFilterModule,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
],
|
||||
declarations: [
|
||||
PremiumBadgeComponent,
|
||||
AcceptEmergencyComponent,
|
||||
AcceptFamilySponsorshipComponent,
|
||||
AcceptOrganizationComponent,
|
||||
AccessComponent,
|
||||
AccountComponent,
|
||||
AddCreditComponent,
|
||||
AddEditComponent,
|
||||
AddEditCustomFieldsComponent,
|
||||
AddEditCustomFieldsComponent,
|
||||
AdjustPaymentComponent,
|
||||
AdjustStorageComponent,
|
||||
AdjustSubscription,
|
||||
ApiKeyComponent,
|
||||
AttachmentsComponent,
|
||||
BreachReportComponent,
|
||||
BulkActionsComponent,
|
||||
BulkDeleteComponent,
|
||||
BulkMoveComponent,
|
||||
BulkRestoreComponent,
|
||||
BulkShareComponent,
|
||||
ChangeEmailComponent,
|
||||
ChangeKdfComponent,
|
||||
ChangePasswordComponent,
|
||||
ChangePlanComponent,
|
||||
CiphersComponent,
|
||||
CollectionsComponent,
|
||||
CreateOrganizationComponent,
|
||||
DeauthorizeSessionsComponent,
|
||||
DeleteAccountComponent,
|
||||
DeleteOrganizationComponent,
|
||||
DisableSendPolicyComponent,
|
||||
DomainRulesComponent,
|
||||
DownloadLicenseComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessComponent,
|
||||
EmergencyAccessConfirmComponent,
|
||||
EmergencyAccessTakeoverComponent,
|
||||
EmergencyAccessViewComponent,
|
||||
EmergencyAddEditComponent,
|
||||
ExportComponent,
|
||||
ExposedPasswordsReportComponent,
|
||||
FamiliesForEnterpriseSetupComponent,
|
||||
FolderAddEditComponent,
|
||||
FooterComponent,
|
||||
FrontendLayoutComponent,
|
||||
GroupingsComponent,
|
||||
HintComponent,
|
||||
ImportComponent,
|
||||
InactiveTwoFactorReportComponent,
|
||||
LinkSsoComponent,
|
||||
LockComponent,
|
||||
LoginComponent,
|
||||
MasterPasswordPolicyComponent,
|
||||
NavbarComponent,
|
||||
NestedCheckboxComponent,
|
||||
OptionsComponent,
|
||||
OrgAccountComponent,
|
||||
OrgAddEditComponent,
|
||||
OrganizationBillingComponent,
|
||||
OrganizationLayoutComponent,
|
||||
OrganizationPlansComponent,
|
||||
OrganizationsComponent,
|
||||
OrganizationSubscriptionComponent,
|
||||
OrgAttachmentsComponent,
|
||||
OrgBulkConfirmComponent,
|
||||
OrgBulkRemoveComponent,
|
||||
OrgBulkStatusComponent,
|
||||
OrgCiphersComponent,
|
||||
OrgCollectionAddEditComponent,
|
||||
OrgCollectionsComponent,
|
||||
OrgEntityEventsComponent,
|
||||
OrgEntityUsersComponent,
|
||||
OrgEventsComponent,
|
||||
OrgExportComponent,
|
||||
OrgExposedPasswordsReportComponent,
|
||||
OrgGroupAddEditComponent,
|
||||
OrgGroupingsComponent,
|
||||
OrgGroupsComponent,
|
||||
OrgImportComponent,
|
||||
OrgInactiveTwoFactorReportComponent,
|
||||
OrgManageCollectionsComponent,
|
||||
OrgManageComponent,
|
||||
OrgPeopleComponent,
|
||||
OrgPoliciesComponent,
|
||||
OrgPolicyEditComponent,
|
||||
OrgResetPasswordComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgSettingComponent,
|
||||
OrgToolsComponent,
|
||||
OrgTwoFactorSetupComponent,
|
||||
OrgUnsecuredWebsitesReportComponent,
|
||||
OrgUserAddEditComponent,
|
||||
OrgUserConfirmComponent,
|
||||
OrgUserGroupsComponent,
|
||||
OrgVaultComponent,
|
||||
OrgWeakPasswordsReportComponent,
|
||||
GeneratorComponent,
|
||||
PasswordGeneratorHistoryComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
PasswordRepromptComponent,
|
||||
PasswordStrengthComponent,
|
||||
PaymentComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
PremiumComponent,
|
||||
ProfileComponent,
|
||||
ProvidersComponent,
|
||||
PurgeVaultComponent,
|
||||
RecoverDeleteComponent,
|
||||
RecoverTwoFactorComponent,
|
||||
RegisterComponent,
|
||||
RemovePasswordComponent,
|
||||
ReportCardComponent,
|
||||
ReportListComponent,
|
||||
ReportsComponent,
|
||||
RequireSsoPolicyComponent,
|
||||
ResetPasswordPolicyComponent,
|
||||
ReusedPasswordsReportComponent,
|
||||
SendAddEditComponent,
|
||||
SendComponent,
|
||||
SendEffluxDatesComponent,
|
||||
SendOptionsPolicyComponent,
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
SingleOrgPolicyComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponent,
|
||||
TaxInfoComponent,
|
||||
ToolsComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
TwoFactorAuthenticatorComponent,
|
||||
TwoFactorComponent,
|
||||
TwoFactorDuoComponent,
|
||||
TwoFactorEmailComponent,
|
||||
TwoFactorOptionsComponent,
|
||||
TwoFactorRecoveryComponent,
|
||||
TwoFactorSetupComponent,
|
||||
TwoFactorVerifyComponent,
|
||||
TwoFactorWebAuthnComponent,
|
||||
TwoFactorYubiKeyComponent,
|
||||
UnsecuredWebsitesReportComponent,
|
||||
UpdateKeyComponent,
|
||||
UpdateLicenseComponent,
|
||||
UpdatePasswordComponent,
|
||||
UpdateTempPasswordComponent,
|
||||
UserBillingComponent,
|
||||
UserLayoutComponent,
|
||||
UserSubscriptionComponent,
|
||||
UserVerificationComponent,
|
||||
VaultComponent,
|
||||
VaultTimeoutInputComponent,
|
||||
VerifyEmailComponent,
|
||||
VerifyEmailTokenComponent,
|
||||
VerifyRecoverDeleteComponent,
|
||||
WeakPasswordsReportComponent,
|
||||
],
|
||||
exports: [FooterComponent, NavbarComponent, OrganizationPlansComponent],
|
||||
providers: [DatePipe],
|
||||
exports: [LooseComponentsModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
|
||||
bootstrap: [],
|
||||
})
|
||||
export class OssModule {}
|
||||
|
@ -1,58 +1,33 @@
|
||||
<ng-container *ngIf="vault">
|
||||
<app-navbar></app-navbar>
|
||||
<div class="container page-content">
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "providers" | i18n }}</h1>
|
||||
</div>
|
||||
<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>
|
||||
</p>
|
||||
<ng-container *ngIf="loaded">
|
||||
<ul class="bwi-ul card-ul carets" *ngIf="providers && providers.length">
|
||||
<li *ngFor="let p of providers">
|
||||
<a [routerLink]="['/providers', p.id]" class="text-body">
|
||||
<i class="bwi bwi-li bwi-caret-right" aria-hidden="true"></i> {{ p.name }}
|
||||
<ng-container *ngIf="!p.enabled">
|
||||
<i
|
||||
class="bwi bwi-exclamation-triangle text-danger"
|
||||
title="{{ 'providerIsDisabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<table class="table table-hover table-list" *ngIf="providers && providers.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let p of providers">
|
||||
<td width="30">
|
||||
<app-avatar [data]="p.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
|
||||
<ng-container *ngIf="!p.enabled">
|
||||
<i
|
||||
class="bwi bwi-exclamation-triangle text-danger"
|
||||
title="{{ 'providerIsDisabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!vault">
|
||||
<app-navbar></app-navbar>
|
||||
<div class="container page-content">
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "providers" | i18n }}</h1>
|
||||
</div>
|
||||
<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>
|
||||
</p>
|
||||
<ng-container *ngIf="loaded">
|
||||
<table class="table table-hover table-list" *ngIf="providers && providers.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let p of providers">
|
||||
<td width="30">
|
||||
<app-avatar [data]="p.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
|
||||
<ng-container *ngIf="!p.enabled">
|
||||
<i
|
||||
class="bwi bwi-exclamation-triangle text-danger"
|
||||
title="{{ 'providerIsDisabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
</ng-container>
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
@ -10,8 +10,6 @@ import { Provider } from "jslib-common/models/domain/provider";
|
||||
templateUrl: "providers.component.html",
|
||||
})
|
||||
export class ProvidersComponent implements OnInit {
|
||||
@Input() vault = false;
|
||||
|
||||
providers: Provider[];
|
||||
loaded = false;
|
||||
actionPromise: Promise<any>;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
||||
import { AuthGuard } from "jslib-angular/guards/auth.guard";
|
||||
|
||||
import { BreachReportComponent } from "./breach-report.component";
|
||||
import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component";
|
||||
@ -16,7 +16,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: ReportsComponent,
|
||||
canActivate: [AuthGuardService],
|
||||
canActivate: [AuthGuard],
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", component: ReportListComponent, data: { homepage: true } },
|
||||
{
|
||||
@ -57,4 +57,4 @@ const routes: Routes = [
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class ReportsModule {}
|
||||
export class ReportsRoutingModule {}
|
@ -19,26 +19,48 @@
|
||||
autocomplete="off"
|
||||
appAutofocus
|
||||
/>
|
||||
<ul class="bwi-ul card-ul">
|
||||
<li [ngClass]="{ active: selectedAll }">
|
||||
<a href="#" appStopClick (click)="selectAll()">
|
||||
<i class="bwi bwi-li bwi-fw bwi-filter"></i>{{ "allSends" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>{{ "types" | i18n }}</h3>
|
||||
<ul class="bwi-ul card-ul">
|
||||
<li [ngClass]="{ active: selectedType === sendType.Text }">
|
||||
<a href="#" appStopClick (click)="selectType(sendType.Text)">
|
||||
<i class="bwi bwi-li bwi-fw bwi-file-text"></i>{{ "sendTypeText" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedType === sendType.File }">
|
||||
<a href="#" appStopClick (click)="selectType(sendType.File)">
|
||||
<i class="bwi bwi-li bwi-fw bwi-file"></i>{{ "sendTypeFile" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="filter">
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option" [ngClass]="{ active: selectedAll }">
|
||||
<span class="filter-buttons">
|
||||
<button bit-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
|
||||
bit-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
|
||||
bit-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>
|
||||
@ -129,39 +151,34 @@
|
||||
<small appStopProp>{{ s.deletionDate | date: "medium" }}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
id="dropdownMenuButton"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||
<button
|
||||
[bitMenuTriggerFor]="sendOptions"
|
||||
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 #sendOptions>
|
||||
<button bit-menu-item (click)="copy(s)">
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copySendLink" | i18n }}
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="copy(s)">
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copySendLink" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="removePassword(s)"
|
||||
*ngIf="s.password && !disableSend"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "removePassword" | i18n }}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(s)">
|
||||
<button
|
||||
bit-menu-item
|
||||
(click)="removePassword(s)"
|
||||
*ngIf="s.password && !disableSend"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "removePassword" | i18n }}
|
||||
</button>
|
||||
<button bit-menu-item (click)="delete(s)">
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -1,40 +0,0 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
|
||||
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationTypeGuardService implements CanActivate {
|
||||
constructor(private organizationService: OrganizationService, private router: Router) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot) {
|
||||
const org = await this.organizationService.get(route.params.organizationId);
|
||||
const permissions = route.data == null ? null : (route.data.permissions as Permissions[]);
|
||||
|
||||
if (
|
||||
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && org.canAccessEventLogs) ||
|
||||
(permissions.indexOf(Permissions.AccessImportExport) !== -1 && org.canAccessImportExport) ||
|
||||
(permissions.indexOf(Permissions.AccessReports) !== -1 && org.canAccessReports) ||
|
||||
(permissions.indexOf(Permissions.CreateNewCollections) !== -1 &&
|
||||
org.canCreateNewCollections) ||
|
||||
(permissions.indexOf(Permissions.EditAnyCollection) !== -1 && org.canEditAnyCollection) ||
|
||||
(permissions.indexOf(Permissions.DeleteAnyCollection) !== -1 && org.canDeleteAnyCollection) ||
|
||||
(permissions.indexOf(Permissions.EditAssignedCollections) !== -1 &&
|
||||
org.canEditAssignedCollections) ||
|
||||
(permissions.indexOf(Permissions.DeleteAssignedCollections) !== -1 &&
|
||||
org.canDeleteAssignedCollections) ||
|
||||
(permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) ||
|
||||
(permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) ||
|
||||
(permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) ||
|
||||
(permissions.indexOf(Permissions.ManageUsers) !== -1 && org.canManageUsers) ||
|
||||
(permissions.indexOf(Permissions.ManageUsersPassword) !== -1 && org.canManageUsersPassword) ||
|
||||
(permissions.indexOf(Permissions.ManageSso) !== -1 && org.canManageSso)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.router.navigate(["/organizations", org.id]);
|
||||
return false;
|
||||
}
|
||||
}
|
@ -39,12 +39,12 @@ import { StateService } from "../../services/state.service";
|
||||
import { StateMigrationService } from "../../services/stateMigration.service";
|
||||
import { WebPlatformUtilsService } from "../../services/webPlatformUtils.service";
|
||||
import { HomeGuard } from "../guards/home.guard";
|
||||
import { PermissionsGuard as OrgPermissionsGuard } from "../organizations/guards/permissions.guard";
|
||||
import { NavigationPermissionsService as OrgPermissionsService } from "../organizations/services/navigation-permissions.service";
|
||||
|
||||
import { EventService } from "./event.service";
|
||||
import { InitService } from "./init.service";
|
||||
import { ModalService } from "./modal.service";
|
||||
import { OrganizationGuardService } from "./organization-guard.service";
|
||||
import { OrganizationTypeGuardService } from "./organization-type-guard.service";
|
||||
import { PolicyListService } from "./policy-list.service";
|
||||
import { RouterService } from "./router.service";
|
||||
|
||||
@ -52,9 +52,9 @@ import { RouterService } from "./router.service";
|
||||
imports: [ToastrModule, JslibServicesModule],
|
||||
declarations: [],
|
||||
providers: [
|
||||
OrgPermissionsService,
|
||||
OrgPermissionsGuard,
|
||||
InitService,
|
||||
OrganizationGuardService,
|
||||
OrganizationTypeGuardService,
|
||||
RouterService,
|
||||
EventService,
|
||||
PolicyListService,
|
||||
|
@ -8,43 +8,19 @@
|
||||
</div>
|
||||
<app-change-email></app-change-email>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showChangePassword">
|
||||
<div class="secondary-header">
|
||||
<h1>{{ "changeMasterPassword" | i18n }}</h1>
|
||||
</div>
|
||||
<app-change-password></app-change-password>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showChangeKdf">
|
||||
<div class="secondary-header">
|
||||
<h1>{{ "encKeySettings" | i18n }}</h1>
|
||||
</div>
|
||||
<app-change-kdf></app-change-kdf>
|
||||
</ng-container>
|
||||
<div class="secondary-header border-0 mb-0">
|
||||
<h1>{{ "apiKey" | i18n }}</h1>
|
||||
</div>
|
||||
<p>
|
||||
{{ "userApiKeyDesc" | i18n }}
|
||||
</p>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="viewUserApiKey()">
|
||||
{{ "viewApiKey" | i18n }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="rotateUserApiKey()">
|
||||
{{ "rotateApiKey" | i18n }}
|
||||
</button>
|
||||
<div class="secondary-header text-danger border-0 mb-0">
|
||||
<h1>{{ "dangerZone" | i18n }}</h1>
|
||||
</div>
|
||||
<div class="card border-danger">
|
||||
<div class="card-body">
|
||||
<p>{{ "dangerZoneDesc" | i18n }}</p>
|
||||
<button type="button" class="btn btn-outline-danger" (click)="deauthorizeSessions()">
|
||||
<button bit-button buttonType="danger" (click)="deauthorizeSessions()">
|
||||
{{ "deauthorizeSessions" | i18n }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger" (click)="purgeVault()">
|
||||
<button bit-button buttonType="danger" (click)="purgeVault()">
|
||||
{{ "purgeVault" | i18n }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger" (click)="deleteAccount()">
|
||||
<button bit-button buttonType="danger" (click)="deleteAccount()">
|
||||
{{ "deleteAccount" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -5,7 +5,6 @@ import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { ApiKeyComponent } from "./api-key.component";
|
||||
import { DeauthorizeSessionsComponent } from "./deauthorize-sessions.component";
|
||||
import { DeleteAccountComponent } from "./delete-account.component";
|
||||
import { PurgeVaultComponent } from "./purge-vault.component";
|
||||
@ -21,13 +20,7 @@ export class AccountComponent {
|
||||
purgeModalRef: ViewContainerRef;
|
||||
@ViewChild("deleteAccountTemplate", { read: ViewContainerRef, static: true })
|
||||
deleteModalRef: ViewContainerRef;
|
||||
@ViewChild("viewUserApiKeyTemplate", { read: ViewContainerRef, static: true })
|
||||
viewUserApiKeyModalRef: ViewContainerRef;
|
||||
@ViewChild("rotateUserApiKeyTemplate", { read: ViewContainerRef, static: true })
|
||||
rotateUserApiKeyModalRef: ViewContainerRef;
|
||||
|
||||
showChangePassword = true;
|
||||
showChangeKdf = true;
|
||||
showChangeEmail = true;
|
||||
|
||||
constructor(
|
||||
@ -38,10 +31,7 @@ export class AccountComponent {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.showChangeEmail =
|
||||
this.showChangeKdf =
|
||||
this.showChangePassword =
|
||||
!(await this.keyConnectorService.getUsesKeyConnector());
|
||||
this.showChangeEmail = !(await this.keyConnectorService.getUsesKeyConnector());
|
||||
}
|
||||
|
||||
async deauthorizeSessions() {
|
||||
@ -55,33 +45,4 @@ export class AccountComponent {
|
||||
async deleteAccount() {
|
||||
await this.modalService.openViewRef(DeleteAccountComponent, this.deleteModalRef);
|
||||
}
|
||||
|
||||
async viewUserApiKey() {
|
||||
const entityId = await this.stateService.getUserId();
|
||||
await this.modalService.openViewRef(ApiKeyComponent, this.viewUserApiKeyModalRef, (comp) => {
|
||||
comp.keyType = "user";
|
||||
comp.entityId = entityId;
|
||||
comp.postKey = this.apiService.postUserApiKey.bind(this.apiService);
|
||||
comp.scope = "api";
|
||||
comp.grantType = "client_credentials";
|
||||
comp.apiKeyTitle = "apiKey";
|
||||
comp.apiKeyWarning = "userApiKeyWarning";
|
||||
comp.apiKeyDescription = "userApiKeyDesc";
|
||||
});
|
||||
}
|
||||
|
||||
async rotateUserApiKey() {
|
||||
const entityId = await this.stateService.getUserId();
|
||||
await this.modalService.openViewRef(ApiKeyComponent, this.rotateUserApiKeyModalRef, (comp) => {
|
||||
comp.keyType = "user";
|
||||
comp.isRotation = true;
|
||||
comp.entityId = entityId;
|
||||
comp.postKey = this.apiService.postUserRotateApiKey.bind(this.apiService);
|
||||
comp.scope = "api";
|
||||
comp.grantType = "client_credentials";
|
||||
comp.apiKeyTitle = "apiKey";
|
||||
comp.apiKeyWarning = "userApiKeyWarning";
|
||||
comp.apiKeyDescription = "apiKeyRotateDesc";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
|
||||
<div class="tabbed-header">
|
||||
<h1>{{ "encKeySettings" | i18n }}</h1>
|
||||
</div>
|
||||
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
@ -68,7 +71,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "changeKdf" | i18n }}</span>
|
||||
</button>
|
||||
|
@ -1,4 +1,8 @@
|
||||
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
|
||||
<div class="tabbed-header">
|
||||
<h1>{{ "changeMasterPassword" | i18n }}</h1>
|
||||
</div>
|
||||
|
||||
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
|
||||
<app-callout
|
||||
type="info"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
@ -83,7 +87,7 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "changeMasterPassword" | i18n }}</span>
|
||||
</button>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from "jslib-angular/components/change-password.component";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
@ -6,6 +7,7 @@ import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
@ -47,7 +49,9 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
private syncService: SyncService,
|
||||
private apiService: ApiService,
|
||||
private sendService: SendService,
|
||||
private organizationService: OrganizationService
|
||||
private organizationService: OrganizationService,
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
private router: Router
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
@ -60,6 +64,12 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (await this.keyConnectorService.getUsesKeyConnector()) {
|
||||
this.router.navigate(["/settings/security/two-factor"]);
|
||||
}
|
||||
}
|
||||
|
||||
async rotateEncKeyClicked() {
|
||||
if (this.rotateEncKey) {
|
||||
const ciphers = await this.cipherService.getAllDecrypted();
|
||||
|
@ -1,155 +0,0 @@
|
||||
<ng-container *ngIf="vault">
|
||||
<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>
|
||||
</p>
|
||||
<ng-container *ngIf="loaded">
|
||||
<ul class="bwi-ul card-ul carets" *ngIf="organizations && organizations.length">
|
||||
<li *ngFor="let o of organizations">
|
||||
<a [routerLink]="['/organizations', o.id]" class="text-body">
|
||||
<i class="bwi bwi-li bwi-caret-right" aria-hidden="true"></i> {{ o.name }}
|
||||
<ng-container *ngIf="!o.enabled">
|
||||
<i
|
||||
class="bwi bwi-exclamation-triangle text-danger"
|
||||
title="{{ 'organizationIsDisabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "organizationIsDisabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p *ngIf="!organizations || !organizations.length">{{ "noOrganizationsList" | i18n }}</p>
|
||||
</ng-container>
|
||||
<a href="#" routerLink="/settings/create-organization" class="btn btn-block btn-outline-primary">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newOrganization" | i18n }}
|
||||
</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!vault">
|
||||
<div class="page-header d-flex">
|
||||
<h1>
|
||||
{{ "organizations" | i18n }}
|
||||
<small [appApiAction]="actionPromise" #action>
|
||||
<ng-container *ngIf="action.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>
|
||||
</h1>
|
||||
<a
|
||||
href="#"
|
||||
routerLink="/settings/create-organization"
|
||||
class="btn btn-sm btn-outline-primary ml-auto"
|
||||
*ngIf="!loaded || (organizations && organizations.length)"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newOrganization" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<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">
|
||||
<ng-container *ngIf="!organizations || !organizations.length">
|
||||
<p>{{ "noOrganizationsList" | i18n }}</p>
|
||||
<a href="#" routerLink="/settings/create-organization" class="btn btn-outline-primary">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newOrganization" | i18n }}
|
||||
</a>
|
||||
</ng-container>
|
||||
<table class="table table-hover table-list" *ngIf="organizations && organizations.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let o of organizations">
|
||||
<td width="30">
|
||||
<app-avatar [data]="o.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" [routerLink]="['/organizations', o.id]">{{ o.name }}</a>
|
||||
<ng-container *ngIf="!o.enabled">
|
||||
<i
|
||||
class="bwi bwi-exclamation-triangle text-danger"
|
||||
title="{{ 'organizationIsDisabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "organizationIsDisabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showEnrolledStatus(o)">
|
||||
<i
|
||||
class="bwi bwi-key"
|
||||
appStopProp
|
||||
title="{{ 'enrolledPasswordReset' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "enrolledPasswordReset" | i18n }}</span>
|
||||
</ng-container>
|
||||
</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
|
||||
*ngIf="allowEnrollmentChanges(o) && !o.resetPasswordEnrolled"
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="toggleResetPasswordEnrollment(o)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
||||
{{ "enrollPasswordReset" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
*ngIf="allowEnrollmentChanges(o) && o.resetPasswordEnrolled"
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="toggleResetPasswordEnrollment(o)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
|
||||
{{ "withdrawPasswordReset" | i18n }}
|
||||
</a>
|
||||
<ng-container *ngIf="o.useSso && o.identifier">
|
||||
<a
|
||||
*ngIf="o.ssoBound; else linkSso"
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="unlinkSso(o)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-chain-broken" aria-hidden="true"></i>
|
||||
{{ "unlinkSso" | i18n }}
|
||||
</a>
|
||||
<ng-template #linkSso>
|
||||
<app-link-sso [organization]="o"> </app-link-sso>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="leave(o)">
|
||||
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
|
||||
{{ "leave" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</ng-container>
|
69
src/app/settings/payment-method.component.html
Normal file
69
src/app/settings/payment-method.component.html
Normal file
@ -0,0 +1,69 @@
|
||||
<div class="tabbed-header d-flex">
|
||||
<h1>
|
||||
{{ "paymentMethod" | i18n }}
|
||||
</h1>
|
||||
<button
|
||||
bit-button
|
||||
buttonType="secondary"
|
||||
(click)="load()"
|
||||
class="tw-ml-auto"
|
||||
*ngIf="firstLoaded"
|
||||
[disabled]="loading"
|
||||
>
|
||||
<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 bit-button buttonType="secondary" (click)="addCredit()" *ngIf="!showAddCredit">
|
||||
{{ "addCredit" | i18n }}
|
||||
</button>
|
||||
<app-add-credit
|
||||
(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">
|
||||
<p>
|
||||
<i
|
||||
class="bwi bwi-fw"
|
||||
[ngClass]="{
|
||||
'bwi-credit-card': paymentSource.type === paymentMethodType.Card,
|
||||
'bwi-bank': paymentSource.type === paymentMethodType.BankAccount,
|
||||
'bwi-money': paymentSource.type === paymentMethodType.Check,
|
||||
'bwi-paypal text-primary': paymentSource.type === paymentMethodType.PayPal,
|
||||
'bwi-apple text-muted': paymentSource.type === paymentMethodType.AppleInApp,
|
||||
'bwi-google text-muted': paymentSource.type === paymentMethodType.GoogleInApp
|
||||
}"
|
||||
></i>
|
||||
<span *ngIf="paymentSourceInApp">{{ "inAppPurchase" | i18n }}</span>
|
||||
{{ paymentSource.description }}
|
||||
</p>
|
||||
</ng-container>
|
||||
<button bit-button buttonType="secondary" (click)="changePayment()" *ngIf="!showAdjustPayment">
|
||||
{{ (paymentSource ? "changePaymentMethod" : "addPaymentMethod") | i18n }}
|
||||
</button>
|
||||
<app-adjust-payment
|
||||
[currentType]="paymentSource != null ? paymentSource.type : null"
|
||||
(onAdjusted)="closePayment(true)"
|
||||
(onCanceled)="closePayment(false)"
|
||||
*ngIf="showAdjustPayment"
|
||||
>
|
||||
</app-adjust-payment>
|
||||
</ng-container>
|
@ -1,40 +1,35 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PaymentMethodType } from "jslib-common/enums/paymentMethodType";
|
||||
import { TransactionType } from "jslib-common/enums/transactionType";
|
||||
import { VerifyBankRequest } from "jslib-common/models/request/verifyBankRequest";
|
||||
import { BillingResponse } from "jslib-common/models/response/billingResponse";
|
||||
import { BillingPaymentResponse } from "jslib-common/models/response/billingPaymentResponse";
|
||||
|
||||
@Component({
|
||||
selector: "app-user-billing",
|
||||
templateUrl: "user-billing.component.html",
|
||||
selector: "app-payment-method",
|
||||
templateUrl: "payment-method.component.html",
|
||||
})
|
||||
export class UserBillingComponent implements OnInit {
|
||||
export class PaymentMethodComponent implements OnInit {
|
||||
loading = false;
|
||||
firstLoaded = false;
|
||||
showAdjustPayment = false;
|
||||
showAddCredit = false;
|
||||
billing: BillingResponse;
|
||||
billing: BillingPaymentResponse;
|
||||
paymentMethodType = PaymentMethodType;
|
||||
transactionType = TransactionType;
|
||||
organizationId: string;
|
||||
verifyAmount1: number;
|
||||
verifyAmount2: number;
|
||||
|
||||
verifyBankPromise: Promise<any>;
|
||||
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
if (this.platformUtilsService.isSelfHost()) {
|
||||
this.router.navigate(["/settings/subscription"]);
|
||||
}
|
||||
await this.load();
|
||||
this.firstLoaded = true;
|
||||
}
|
||||
@ -44,39 +39,10 @@ export class UserBillingComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
if (this.organizationId != null) {
|
||||
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
|
||||
} else {
|
||||
this.billing = await this.apiService.getUserBilling();
|
||||
}
|
||||
this.billing = await this.apiService.getUserBillingPayment();
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async verifyBank() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const request = new VerifyBankRequest();
|
||||
request.amount1 = this.verifyAmount1;
|
||||
request.amount2 = this.verifyAmount2;
|
||||
this.verifyBankPromise = this.apiService.postOrganizationVerifyBank(
|
||||
this.organizationId,
|
||||
request
|
||||
);
|
||||
await this.verifyBankPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("verifiedBankAccount")
|
||||
);
|
||||
this.load();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
addCredit() {
|
||||
if (this.paymentSourceInApp) {
|
||||
this.platformUtilsService.showDialog(
|
||||
@ -138,12 +104,4 @@ export class UserBillingComponent implements OnInit {
|
||||
this.paymentSource.type === PaymentMethodType.GoogleInApp)
|
||||
);
|
||||
}
|
||||
|
||||
get invoices() {
|
||||
return this.billing != null ? this.billing.invoices : null;
|
||||
}
|
||||
|
||||
get transactions() {
|
||||
return this.billing != null ? this.billing.transactions : null;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "options" | i18n }}</h1>
|
||||
<h1>{{ "preferences" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{ "optionsDesc" | i18n }}</p>
|
||||
<p>{{ "preferencesDesc" | i18n }}</p>
|
||||
<form (ngSubmit)="submit()" ngNativeValidate>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
@ -10,10 +10,10 @@ import { ThemeType } from "jslib-common/enums/themeType";
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
|
||||
@Component({
|
||||
selector: "app-options",
|
||||
templateUrl: "options.component.html",
|
||||
selector: "app-preferences",
|
||||
templateUrl: "preferences.component.html",
|
||||
})
|
||||
export class OptionsComponent implements OnInit {
|
||||
export class PreferencesComponent implements OnInit {
|
||||
vaultTimeoutAction = "lock";
|
||||
disableIcons: boolean;
|
||||
enableGravatars: boolean;
|
||||
@ -107,7 +107,11 @@ export class OptionsComponent implements OnInit {
|
||||
if (this.locale !== this.startingLocale) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("optionsUpdated"));
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("preferencesUpdated")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,18 @@
|
||||
<div class="page-header">
|
||||
<div *ngIf="selfHosted" class="page-header">
|
||||
<h1>{{ "subscription" | i18n }}</h1>
|
||||
</div>
|
||||
<div *ngIf="!selfHosted" class="tabbed-header">
|
||||
<h1>{{ "goPremium" | i18n }}</h1>
|
||||
</div>
|
||||
<app-callout
|
||||
<bit-callout
|
||||
type="info"
|
||||
*ngIf="canAccessPremium"
|
||||
title="{{ 'youHavePremiumAccess' | i18n }}"
|
||||
icon="bwi bwi-star-f"
|
||||
>
|
||||
{{ "alreadyPremiumFromOrg" | i18n }}
|
||||
</app-callout>
|
||||
<app-callout type="success">
|
||||
</bit-callout>
|
||||
<bit-callout type="success">
|
||||
<p>{{ "premiumUpgradeUnlockFeatures" | i18n }}</p>
|
||||
<ul class="bwi-ul">
|
||||
<li>
|
||||
@ -45,15 +48,16 @@
|
||||
{{ "premiumPrice" | i18n: (premiumPrice | currency: "$") }}
|
||||
</p>
|
||||
<a
|
||||
bit-button
|
||||
href="https://vault.bitwarden.com/#/settings/premium"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="btn btn-outline-secondary"
|
||||
buttonType="secondary"
|
||||
*ngIf="selfHosted"
|
||||
>
|
||||
{{ "purchasePremium" | i18n }}
|
||||
</a>
|
||||
</app-callout>
|
||||
</bit-callout>
|
||||
<ng-container *ngIf="selfHosted">
|
||||
<p>{{ "uploadLicenseFilePremium" | i18n }}</p>
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
@ -64,7 +68,13 @@
|
||||
"licenseFileDesc" | i18n: "bitwarden_premium_license.json"
|
||||
}}</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<button
|
||||
bit-button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
class="btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "submit" | i18n }}</span>
|
||||
</button>
|
||||
@ -115,7 +125,13 @@
|
||||
</p>
|
||||
</div>
|
||||
<small class="text-muted font-italic">{{ "paymentChargedAnnually" | i18n }}</small>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<button
|
||||
bit-button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
class="btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "submit" | i18n }}</span>
|
||||
</button>
|
||||
|
@ -47,7 +47,7 @@ export class PremiumComponent implements OnInit {
|
||||
this.canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
const premium = await this.tokenService.getPremium();
|
||||
if (premium) {
|
||||
this.router.navigate(["/settings/subscription"]);
|
||||
this.router.navigate(["/settings/subscription/user-subscription"]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -119,7 +119,7 @@ export class PremiumComponent implements OnInit {
|
||||
await this.syncService.fullSync(true);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("premiumUpdated"));
|
||||
this.messagingService.send("purchasedPremium");
|
||||
this.router.navigate(["/settings/subscription"]);
|
||||
this.router.navigate(["/settings/subscription/user-subscription"]);
|
||||
}
|
||||
|
||||
get additionalStorageTotal(): number {
|
||||
|
18
src/app/settings/security-keys.component.html
Normal file
18
src/app/settings/security-keys.component.html
Normal file
@ -0,0 +1,18 @@
|
||||
<app-change-kdf *ngIf="showChangeKdf"></app-change-kdf>
|
||||
<div
|
||||
[ngClass]="{ 'tabbed-header': !showChangeKdf, 'secondary-header': showChangeKdf }"
|
||||
class="border-0 mb-0"
|
||||
>
|
||||
<h1>{{ "apiKey" | i18n }}</h1>
|
||||
</div>
|
||||
<p>
|
||||
{{ "userApiKeyDesc" | i18n }}
|
||||
</p>
|
||||
<button bit-button buttonType="secondary" (click)="viewUserApiKey()">
|
||||
{{ "viewApiKey" | i18n }}
|
||||
</button>
|
||||
<button bit-button buttonType="secondary" (click)="rotateUserApiKey()">
|
||||
{{ "rotateApiKey" | i18n }}
|
||||
</button>
|
||||
<ng-template #viewUserApiKeyTemplate></ng-template>
|
||||
<ng-template #rotateUserApiKeyTemplate></ng-template>
|
61
src/app/settings/security-keys.component.ts
Normal file
61
src/app/settings/security-keys.component.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { ApiKeyComponent } from "./api-key.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-security-keys",
|
||||
templateUrl: "security-keys.component.html",
|
||||
})
|
||||
export class SecurityKeysComponent implements OnInit {
|
||||
@ViewChild("viewUserApiKeyTemplate", { read: ViewContainerRef, static: true })
|
||||
viewUserApiKeyModalRef: ViewContainerRef;
|
||||
@ViewChild("rotateUserApiKeyTemplate", { read: ViewContainerRef, static: true })
|
||||
rotateUserApiKeyModalRef: ViewContainerRef;
|
||||
|
||||
showChangeKdf = true;
|
||||
|
||||
constructor(
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
private stateService: StateService,
|
||||
private modalService: ModalService,
|
||||
private apiService: ApiService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.showChangeKdf = !(await this.keyConnectorService.getUsesKeyConnector());
|
||||
}
|
||||
|
||||
async viewUserApiKey() {
|
||||
const entityId = await this.stateService.getUserId();
|
||||
await this.modalService.openViewRef(ApiKeyComponent, this.viewUserApiKeyModalRef, (comp) => {
|
||||
comp.keyType = "user";
|
||||
comp.entityId = entityId;
|
||||
comp.postKey = this.apiService.postUserApiKey.bind(this.apiService);
|
||||
comp.scope = "api";
|
||||
comp.grantType = "client_credentials";
|
||||
comp.apiKeyTitle = "apiKey";
|
||||
comp.apiKeyWarning = "userApiKeyWarning";
|
||||
comp.apiKeyDescription = "userApiKeyDesc";
|
||||
});
|
||||
}
|
||||
|
||||
async rotateUserApiKey() {
|
||||
const entityId = await this.stateService.getUserId();
|
||||
await this.modalService.openViewRef(ApiKeyComponent, this.rotateUserApiKeyModalRef, (comp) => {
|
||||
comp.keyType = "user";
|
||||
comp.isRotation = true;
|
||||
comp.entityId = entityId;
|
||||
comp.postKey = this.apiService.postUserRotateApiKey.bind(this.apiService);
|
||||
comp.scope = "api";
|
||||
comp.grantType = "client_credentials";
|
||||
comp.apiKeyTitle = "apiKey";
|
||||
comp.apiKeyWarning = "userApiKeyWarning";
|
||||
comp.apiKeyDescription = "apiKeyRotateDesc";
|
||||
});
|
||||
}
|
||||
}
|
39
src/app/settings/security-routing.module.ts
Normal file
39
src/app/settings/security-routing.module.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { ChangePasswordComponent } from "./change-password.component";
|
||||
import { SecurityKeysComponent } from "./security-keys.component";
|
||||
import { SecurityComponent } from "./security.component";
|
||||
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: SecurityComponent,
|
||||
data: { titleId: "security" },
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "change-password" },
|
||||
{
|
||||
path: "change-password",
|
||||
component: ChangePasswordComponent,
|
||||
data: { titleId: "masterPassword" },
|
||||
},
|
||||
{
|
||||
path: "two-factor",
|
||||
component: TwoFactorSetupComponent,
|
||||
data: { titleId: "twoStepLogin" },
|
||||
},
|
||||
{
|
||||
path: "security-keys",
|
||||
component: SecurityKeysComponent,
|
||||
data: { titleId: "keys" },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class SecurityRoutingModule {}
|
22
src/app/settings/security.component.html
Normal file
22
src/app/settings/security.component.html
Normal file
@ -0,0 +1,22 @@
|
||||
<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>
|
17
src/app/settings/security.component.ts
Normal file
17
src/app/settings/security.component.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-security",
|
||||
templateUrl: "security.component.html",
|
||||
})
|
||||
export class SecurityComponent {
|
||||
showChangePassword = true;
|
||||
|
||||
constructor(private keyConnectorService: KeyConnectorService) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.showChangePassword = !(await this.keyConnectorService.getUsesKeyConnector());
|
||||
}
|
||||
}
|
@ -7,38 +7,19 @@
|
||||
<a routerLink="account" class="list-group-item" routerLinkActive="active">
|
||||
{{ "myAccount" | i18n }}
|
||||
</a>
|
||||
<a routerLink="options" class="list-group-item" routerLinkActive="active">
|
||||
{{ "options" | i18n }}
|
||||
<a routerLink="security" class="list-group-item" routerLinkActive="active">
|
||||
{{ "security" | i18n }}
|
||||
</a>
|
||||
<a routerLink="organizations" class="list-group-item" routerLinkActive="active">
|
||||
{{ "organizations" | i18n }}
|
||||
<a routerLink="preferences" class="list-group-item" routerLinkActive="active">
|
||||
{{ "preferences" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="subscription"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="premium"
|
||||
*ngIf="!hideSubscription"
|
||||
>
|
||||
{{ "premiumMembership" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="premium"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="!premium"
|
||||
>
|
||||
{{ "goPremium" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="billing"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="!selfHosted"
|
||||
>
|
||||
{{ "billing" | i18n }}
|
||||
</a>
|
||||
<a routerLink="two-factor" class="list-group-item" routerLinkActive="active">
|
||||
{{ "twoStepLogin" | i18n }}
|
||||
{{ "subscription" | i18n }}
|
||||
</a>
|
||||
<a routerLink="domain-rules" class="list-group-item" routerLinkActive="active">
|
||||
{{ "domainRules" | i18n }}
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
|
||||
const BroadcasterSubscriptionId = "SettingsComponent";
|
||||
|
||||
@Component({
|
||||
@ -15,13 +18,16 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
premium: boolean;
|
||||
selfHosted: boolean;
|
||||
hasFamilySponsorshipAvailable: boolean;
|
||||
hideSubscription: boolean;
|
||||
|
||||
constructor(
|
||||
private tokenService: TokenService,
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationService: OrganizationService
|
||||
private organizationService: OrganizationService,
|
||||
private stateService: StateService,
|
||||
private apiService: ApiService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -47,5 +53,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
async load() {
|
||||
this.premium = await this.tokenService.getPremium();
|
||||
this.hasFamilySponsorshipAvailable = await this.organizationService.canManageSponsorships();
|
||||
const hasPremiumFromOrg = await this.stateService.getCanAccessPremium();
|
||||
const billing = await this.apiService.getUserBillingHistory();
|
||||
this.hideSubscription = !this.premium && hasPremiumFromOrg && billing.hasNoHistory;
|
||||
}
|
||||
}
|
||||
|
45
src/app/settings/subscription-routing.module.ts
Normal file
45
src/app/settings/subscription-routing.module.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { PaymentMethodComponent } from "./payment-method.component";
|
||||
import { PremiumComponent } from "./premium.component";
|
||||
import { SubscriptionComponent } from "./subscription.component";
|
||||
import { UserBillingHistoryComponent } from "./user-billing-history.component";
|
||||
import { UserSubscriptionComponent } from "./user-subscription.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: SubscriptionComponent,
|
||||
data: { titleId: "subscription" },
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "premium" },
|
||||
{
|
||||
path: "user-subscription",
|
||||
component: UserSubscriptionComponent,
|
||||
data: { titleId: "premiumMembership" },
|
||||
},
|
||||
{
|
||||
path: "premium",
|
||||
component: PremiumComponent,
|
||||
data: { titleId: "goPremium" },
|
||||
},
|
||||
{
|
||||
path: "payment-method",
|
||||
component: PaymentMethodComponent,
|
||||
data: { titleId: "paymentMethod" },
|
||||
},
|
||||
{
|
||||
path: "billing-history",
|
||||
component: UserBillingHistoryComponent,
|
||||
data: { titleId: "billingHistory" },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class SubscriptionRoutingModule {}
|
20
src/app/settings/subscription.component.html
Normal file
20
src/app/settings/subscription.component.html
Normal file
@ -0,0 +1,20 @@
|
||||
<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>
|
27
src/app/settings/subscription.component.ts
Normal file
27
src/app/settings/subscription.component.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-subscription",
|
||||
templateUrl: "subscription.component.html",
|
||||
})
|
||||
export class SubscriptionComponent {
|
||||
hasPremium: boolean;
|
||||
selfHosted: boolean;
|
||||
|
||||
constructor(
|
||||
private tokenService: TokenService,
|
||||
private platformUtilsService: PlatformUtilsService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.hasPremium = await this.tokenService.getPremium();
|
||||
this.selfHosted = this.platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
get subscriptionRoute(): string {
|
||||
return this.hasPremium ? "user-subscription" : "premium";
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
<div class="page-header">
|
||||
<div class="tabbed-header">
|
||||
<h1>{{ "twoStepLogin" | i18n }}</h1>
|
||||
</div>
|
||||
<p *ngIf="!organizationId">{{ "twoStepLoginDesc" | i18n }}</p>
|
||||
<p *ngIf="organizationId">{{ "twoStepLoginOrganizationDesc" | i18n }}</p>
|
||||
<app-callout type="warning" *ngIf="!organizationId">
|
||||
<bit-callout type="warning" *ngIf="!organizationId">
|
||||
<p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="recoveryCode()">
|
||||
<button bit-button buttonType="secondary" (click)="recoveryCode()">
|
||||
{{ "viewRecoveryCode" | i18n }}
|
||||
</button>
|
||||
</app-callout>
|
||||
</bit-callout>
|
||||
<h2 [ngClass]="{ 'mt-5': !organizationId }">
|
||||
{{ "providers" | i18n }}
|
||||
<small *ngIf="loading">
|
||||
@ -20,9 +20,9 @@
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</small>
|
||||
</h2>
|
||||
<app-callout type="warning" *ngIf="showPolicyWarning">
|
||||
<bit-callout type="warning" *ngIf="showPolicyWarning">
|
||||
{{ "twoStepLoginPolicyUserWarning" | i18n }}
|
||||
</app-callout>
|
||||
</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">
|
||||
@ -45,8 +45,8 @@
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
bit-button
|
||||
buttonType="secondary"
|
||||
[disabled]="!canAccessPremium && p.premium"
|
||||
(click)="manage(p.type)"
|
||||
>
|
||||
|
98
src/app/settings/user-billing-history.component.html
Normal file
98
src/app/settings/user-billing-history.component.html
Normal file
@ -0,0 +1,98 @@
|
||||
<div class="tabbed-header d-flex">
|
||||
<h1>
|
||||
{{ "billingHistory" | i18n }}
|
||||
</h1>
|
||||
<button
|
||||
bit-button
|
||||
buttonType="secondary"
|
||||
(click)="load()"
|
||||
class="tw-ml-auto"
|
||||
*ngIf="firstLoaded"
|
||||
[disabled]="loading"
|
||||
>
|
||||
<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>{{ "invoices" | i18n }}</h2>
|
||||
<p *ngIf="!invoices || !invoices.length">{{ "noInvoices" | i18n }}</p>
|
||||
<table class="table mb-2" *ngIf="invoices && invoices.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let i of invoices">
|
||||
<td>{{ i.date | date: "mediumDate" }}</td>
|
||||
<td>
|
||||
<a
|
||||
href="{{ i.pdfUrl }}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="mr-2"
|
||||
appA11yTitle="{{ 'downloadInvoice' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-file-pdf" aria-hidden="true"></i
|
||||
></a>
|
||||
<a href="{{ i.url }}" target="_blank" rel="noopener" title="{{ 'viewInvoice' | i18n }}">
|
||||
{{ "invoiceNumber" | i18n: i.number }}</a
|
||||
>
|
||||
</td>
|
||||
<td>{{ i.amount | currency: "$" }}</td>
|
||||
<td>
|
||||
<span *ngIf="i.paid">
|
||||
<i class="bwi bwi-check text-success" aria-hidden="true"></i>
|
||||
{{ "paid" | i18n }}
|
||||
</span>
|
||||
<span *ngIf="!i.paid">
|
||||
<i class="bwi bwi-exclamation-circle text-muted" aria-hidden="true"></i>
|
||||
{{ "unpaid" | i18n }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 class="spaced-header">{{ "transactions" | i18n }}</h2>
|
||||
<p *ngIf="!transactions || !transactions.length">{{ "noTransactions" | i18n }}</p>
|
||||
<table class="table mb-2" *ngIf="transactions && transactions.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let t of transactions">
|
||||
<td>{{ t.createdDate | date: "mediumDate" }}</td>
|
||||
<td>
|
||||
<span *ngIf="t.type === transactionType.Charge || t.type === transactionType.Credit">
|
||||
{{ "chargeNoun" | i18n }}
|
||||
</span>
|
||||
<span *ngIf="t.type === transactionType.Refund">{{ "refundNoun" | i18n }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<i
|
||||
class="bwi bwi-fw"
|
||||
*ngIf="t.paymentMethodType"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-credit-card': t.paymentMethodType === paymentMethodType.Card,
|
||||
'bwi-bank':
|
||||
t.paymentMethodType === paymentMethodType.BankAccount ||
|
||||
t.paymentMethodType === paymentMethodType.WireTransfer,
|
||||
'bwi-bitcoin text-warning': t.paymentMethodType === paymentMethodType.BitPay,
|
||||
'bwi-paypal text-primary': t.paymentMethodType === paymentMethodType.PayPal
|
||||
}"
|
||||
></i>
|
||||
{{ t.details }}
|
||||
</td>
|
||||
<td
|
||||
[ngClass]="{ 'text-strike': t.refunded }"
|
||||
title="{{ (t.refunded ? 'refunded' : '') | i18n }}"
|
||||
>
|
||||
{{ t.amount | currency: "$" }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<small class="text-muted">* {{ "chargesStatement" | i18n: "BITWARDEN" }}</small>
|
||||
</ng-container>
|
53
src/app/settings/user-billing-history.component.ts
Normal file
53
src/app/settings/user-billing-history.component.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PaymentMethodType } from "jslib-common/enums/paymentMethodType";
|
||||
import { TransactionType } from "jslib-common/enums/transactionType";
|
||||
import { BillingHistoryResponse } from "jslib-common/models/response/billingHistoryResponse";
|
||||
|
||||
@Component({
|
||||
selector: "app-user-billing",
|
||||
templateUrl: "user-billing-history.component.html",
|
||||
})
|
||||
export class UserBillingHistoryComponent implements OnInit {
|
||||
loading = false;
|
||||
firstLoaded = false;
|
||||
billing: BillingHistoryResponse;
|
||||
paymentMethodType = PaymentMethodType;
|
||||
transactionType = TransactionType;
|
||||
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
if (this.platformUtilsService.isSelfHost()) {
|
||||
this.router.navigate(["/settings/subscription"]);
|
||||
}
|
||||
await this.load();
|
||||
this.firstLoaded = true;
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
this.billing = await this.apiService.getUserBillingHistory();
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
get invoices() {
|
||||
return this.billing != null ? this.billing.invoices : null;
|
||||
}
|
||||
|
||||
get transactions() {
|
||||
return this.billing != null ? this.billing.transactions : null;
|
||||
}
|
||||
}
|
@ -1,6 +1,11 @@
|
||||
<div class="page-header">
|
||||
<div
|
||||
[ngClass]="{
|
||||
'page-header': selfHosted,
|
||||
'tabbed-header': !selfHosted
|
||||
}"
|
||||
>
|
||||
<h1>
|
||||
{{ "premiumMembership" | i18n }}
|
||||
{{ title }}
|
||||
<small *ngIf="firstLoaded && loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
@ -20,23 +25,25 @@
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="sub">
|
||||
<app-callout
|
||||
<bit-callout
|
||||
type="warning"
|
||||
title="{{ 'canceled' | i18n }}"
|
||||
*ngIf="subscription && subscription.cancelled"
|
||||
>
|
||||
{{ "subscriptionCanceled" | i18n }}</app-callout
|
||||
{{ "subscriptionCanceled" | i18n }}</bit-callout
|
||||
>
|
||||
<app-callout
|
||||
<bit-callout
|
||||
type="warning"
|
||||
title="{{ 'pendingCancellation' | i18n }}"
|
||||
*ngIf="subscriptionMarkedForCancel"
|
||||
>
|
||||
<p>{{ "subscriptionPendingCanceled" | i18n }}</p>
|
||||
<button
|
||||
#reinstateBtn
|
||||
bit-button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-submit"
|
||||
buttonType="secondary"
|
||||
#reinstateBtn
|
||||
class="btn-submit"
|
||||
(click)="reinstate()"
|
||||
[appApiAction]="reinstatePromise"
|
||||
[disabled]="reinstateBtn.loading"
|
||||
@ -44,7 +51,7 @@
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "reinstateSubscription" | i18n }}</span>
|
||||
</button>
|
||||
</app-callout>
|
||||
</bit-callout>
|
||||
<dl *ngIf="selfHosted">
|
||||
<dt>{{ "expiration" | i18n }}</dt>
|
||||
<dd *ngIf="sub.expiration">{{ sub.expiration | date: "mediumDate" }}</dd>
|
||||
@ -56,7 +63,7 @@
|
||||
<dt>{{ "status" | i18n }}</dt>
|
||||
<dd>
|
||||
<span class="text-capitalize">{{ (subscription && subscription.status) || "-" }}</span>
|
||||
<span class="badge badge-warning" *ngIf="subscriptionMarkedForCancel">{{
|
||||
<span bit-badge badgeType="warning" *ngIf="subscriptionMarkedForCancel">{{
|
||||
"pendingCancellation" | i18n
|
||||
}}</span>
|
||||
</dd>
|
||||
@ -89,14 +96,15 @@
|
||||
</div>
|
||||
<ng-container *ngIf="selfHosted">
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
|
||||
<button type="button" bit-button buttonType="secondary" (click)="updateLicense()">
|
||||
{{ "updateLicense" | i18n }}
|
||||
</button>
|
||||
<a
|
||||
bit-button
|
||||
buttonType="secondary"
|
||||
href="https://vault.bitwarden.com/#/settings/subscription"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="btn btn-outline-secondary"
|
||||
>
|
||||
{{ "manageSubscription" | i18n }}
|
||||
</a>
|
||||
@ -123,17 +131,20 @@
|
||||
<ng-container *ngIf="!selfHosted">
|
||||
<div class="d-flex">
|
||||
<button
|
||||
bit-button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
buttonType="secondary"
|
||||
(click)="downloadLicense()"
|
||||
*ngIf="!subscription || !subscription.cancelled"
|
||||
>
|
||||
{{ "downloadLicense" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bit-button
|
||||
#cancelBtn
|
||||
type="button"
|
||||
class="btn btn-outline-danger btn-submit ml-auto"
|
||||
buttonType="danger"
|
||||
class="btn-submit tw-ml-auto"
|
||||
(click)="cancel()"
|
||||
[appApiAction]="cancelPromise"
|
||||
[disabled]="cancelBtn.loading"
|
||||
@ -160,12 +171,14 @@
|
||||
<ng-container *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
|
||||
<div class="mt-3">
|
||||
<div class="d-flex" *ngIf="!showAdjustStorage">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="adjustStorage(true)">
|
||||
<button bit-button type="button" buttonType="secondary" (click)="adjustStorage(true)">
|
||||
{{ "addStorage" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bit-button
|
||||
type="button"
|
||||
class="ml-1 btn btn-outline-secondary"
|
||||
buttonType="secondary"
|
||||
class="tw-ml-1"
|
||||
(click)="adjustStorage(false)"
|
||||
>
|
||||
{{ "removeStorage" | i18n }}
|
||||
|
@ -49,7 +49,7 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
this.loading = true;
|
||||
this.sub = await this.apiService.getUserSubscription();
|
||||
} else {
|
||||
this.router.navigate(["/settings/premium"]);
|
||||
this.router.navigate(["/settings/subscription/premium"]);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -210,4 +210,8 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
get usingInAppPurchase() {
|
||||
return this.sub != null ? this.sub.usingInAppPurchase : false;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this.i18nService.t(this.selfHosted ? "subscription" : "premiumMembership");
|
||||
}
|
||||
}
|
||||
|
@ -24,15 +24,6 @@
|
||||
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"
|
||||
@ -54,121 +45,88 @@
|
||||
<br />
|
||||
<small appStopProp>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td *ngIf="organizations.length > 0 && !organization" class="tw-w-28">
|
||||
<app-org-badge
|
||||
organizationName="{{ c.organizationId | orgNameFromId: organizations }}"
|
||||
[color]="!c.organizationId ? '#175ddc' : null"
|
||||
(onOrganizationClicked)="onOrganizationClicked(c.organizationId)"
|
||||
></app-org-badge>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
id="dropdownMenuButton"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||
<button
|
||||
[bitMenuTriggerFor]="cipherOptions"
|
||||
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 #cipherOptions>
|
||||
<ng-container *ngIf="c.type === cipherType.Login && !c.isDeleted">
|
||||
<button bit-menu-item (click)="copy(c, c.login.username, 'username', 'Username')">
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copyUsername" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bit-menu-item
|
||||
(click)="copy(c, c.login.password, 'password', 'Password')"
|
||||
*ngIf="c.viewPassword"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copyPassword" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bit-menu-item
|
||||
(click)="copy(c, c.login.totp, 'verificationCodeTotp', 'TOTP')"
|
||||
*ngIf="displayTotpCopyButton(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copyVerificationCode" | i18n }}
|
||||
</button>
|
||||
<button bit-menu-item *ngIf="c.login.canLaunch" (click)="launch(c.login.launchUri)">
|
||||
<i class="bwi bwi-fw bwi-share-square" aria-hidden="true"></i>
|
||||
{{ "launch" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<button bit-menu-item (click)="attachments(c)">
|
||||
<i class="bwi bwi-fw bwi-paperclip" aria-hidden="true"></i>
|
||||
{{ "attachments" | i18n }}
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<ng-container *ngIf="c.type === cipherType.Login && !c.isDeleted">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="copy(c, c.login.username, 'username', 'Username')"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copyUsername" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="copy(c, c.login.password, 'password', 'Password')"
|
||||
*ngIf="c.viewPassword"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copyPassword" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="copy(c, c.login.totp, 'verificationCodeTotp', 'TOTP')"
|
||||
*ngIf="displayTotpCopyButton(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copyVerificationCode" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="c.login.canLaunch"
|
||||
(click)="launch(c.login.launchUri)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-share-square" aria-hidden="true"></i>
|
||||
{{ "launch" | i18n }}
|
||||
</a>
|
||||
</ng-container>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="attachments(c)">
|
||||
<i class="bwi bwi-fw bwi-paperclip" aria-hidden="true"></i>
|
||||
{{ "attachments" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="((!organization && !c.organizationId) || organization) && !c.isDeleted"
|
||||
(click)="clone(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-files" aria-hidden="true"></i>
|
||||
{{ "clone" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="!organization && !c.organizationId && !c.isDeleted"
|
||||
(click)="share(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-arrow-circle-right" aria-hidden="true"></i>
|
||||
{{ "moveToOrganization" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="c.organizationId && !c.isDeleted"
|
||||
(click)="collections(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
||||
{{ "collections" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="c.organizationId && accessEvents"
|
||||
(click)="events(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-file-text" aria-hidden="true"></i>
|
||||
{{ "eventLogs" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="restore(c)"
|
||||
*ngIf="c.isDeleted"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
|
||||
{{ "restore" | i18n }}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(c)">
|
||||
<button
|
||||
bit-menu-item
|
||||
*ngIf="((!organization && !c.organizationId) || organization) && !c.isDeleted"
|
||||
(click)="clone(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-files" aria-hidden="true"></i>
|
||||
{{ "clone" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bit-menu-item
|
||||
*ngIf="!organization && !c.organizationId && !c.isDeleted"
|
||||
(click)="share(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-arrow-circle-right" aria-hidden="true"></i>
|
||||
{{ "moveToOrganization" | i18n }}
|
||||
</button>
|
||||
<button bit-menu-item *ngIf="c.organizationId && !c.isDeleted" (click)="collections(c)">
|
||||
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
||||
{{ "collections" | i18n }}
|
||||
</button>
|
||||
<button bit-menu-item *ngIf="c.organizationId && accessEvents" (click)="events(c)">
|
||||
<i class="bwi bwi-fw bwi-file-text" aria-hidden="true"></i>
|
||||
{{ "eventLogs" | i18n }}
|
||||
</button>
|
||||
<button bit-menu-item (click)="restore(c)" *ngIf="c.isDeleted">
|
||||
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
|
||||
{{ "restore" | i18n }}
|
||||
</button>
|
||||
<button bit-menu-item (click)="delete(c)">
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ (c.isDeleted ? "permanentlyDelete" : "delete") | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -5,6 +5,7 @@ import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { EventService } from "jslib-common/abstractions/event.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||
@ -13,6 +14,7 @@ import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
import { EventType } from "jslib-common/enums/eventType";
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
const MaxCheckedCount = 500;
|
||||
@ -27,12 +29,14 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
|
||||
@Output() onShareClicked = new EventEmitter<CipherView>();
|
||||
@Output() onCollectionsClicked = new EventEmitter<CipherView>();
|
||||
@Output() onCloneClicked = new EventEmitter<CipherView>();
|
||||
@Output() onOrganzationBadgeClicked = new EventEmitter<string>();
|
||||
|
||||
pagedCiphers: CipherView[] = [];
|
||||
pageSize = 200;
|
||||
cipherType = CipherType;
|
||||
actionPromise: Promise<any>;
|
||||
userHasPremiumAccess = false;
|
||||
organizations: Organization[] = [];
|
||||
|
||||
private didScroll = false;
|
||||
private pagedCiphersCount = 0;
|
||||
@ -47,7 +51,8 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
|
||||
protected totpService: TotpService,
|
||||
protected stateService: StateService,
|
||||
protected passwordRepromptService: PasswordRepromptService,
|
||||
private logService: LogService
|
||||
private logService: LogService,
|
||||
private organizationService: OrganizationService
|
||||
) {
|
||||
super(searchService);
|
||||
}
|
||||
@ -60,6 +65,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
|
||||
// Do not use ngOnInit() for anything that requires sync data.
|
||||
async load(filter: (cipher: CipherView) => boolean = null, deleted = false) {
|
||||
await super.load(filter, deleted);
|
||||
this.organizations = await this.organizationService.getAll();
|
||||
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium();
|
||||
}
|
||||
|
||||
@ -273,6 +279,10 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
|
||||
}
|
||||
}
|
||||
|
||||
onOrganizationClicked(organizationId: string) {
|
||||
this.onOrganzationBadgeClicked.emit(organizationId);
|
||||
}
|
||||
|
||||
protected deleteCipher(id: string, permanent: boolean) {
|
||||
return permanent
|
||||
? this.cipherService.deleteWithServer(id)
|
||||
|
@ -1,173 +0,0 @@
|
||||
<div class="card vault-filters">
|
||||
<div class="card-header d-flex">
|
||||
{{ "filters" | i18n }}
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://bitwarden.com/help/searching-vault/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="{{ searchPlaceholder || ('searchVault' | i18n) }}"
|
||||
id="search"
|
||||
class="form-control"
|
||||
[(ngModel)]="searchText"
|
||||
(input)="searchTextChanged()"
|
||||
autocomplete="off"
|
||||
appAutofocus
|
||||
/>
|
||||
<ul class="bwi-ul card-ul">
|
||||
<li [ngClass]="{ active: selectedAll }">
|
||||
<a href="#" appStopClick (click)="selectAll()">
|
||||
<i class="bwi bwi-li bwi-fw bwi-filter"></i>{{ "allItems" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedFavorites }" *ngIf="showFavorites">
|
||||
<a href="#" appStopClick (click)="selectFavorites()">
|
||||
<i class="bwi bwi-li bwi-fw bwi-star"></i>{{ "favorites" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedTrash }" *ngIf="showTrash">
|
||||
<a href="#" appStopClick (click)="selectTrash()">
|
||||
<i class="bwi bwi-li bwi-fw bwi-trash"></i>{{ "trash" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>{{ "types" | i18n }}</h3>
|
||||
<ul class="bwi-ul card-ul">
|
||||
<li [ngClass]="{ active: selectedType === cipherType.Login }">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.Login)">
|
||||
<i class="bwi bwi-li bwi-fw bwi-globe"></i>{{ "typeLogin" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedType === cipherType.Card }">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.Card)">
|
||||
<i class="bwi bwi-li bwi-fw bwi-credit-card"></i>{{ "typeCard" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedType === cipherType.Identity }">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.Identity)">
|
||||
<i class="bwi bwi-li bwi-fw bwi-id-card"></i>{{ "typeIdentity" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedType === cipherType.SecureNote }">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.SecureNote)">
|
||||
<i class="bwi bwi-li bwi-fw bwi-sticky-note"></i>{{ "typeSecureNote" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<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>
|
||||
</p>
|
||||
<ng-container *ngIf="loaded">
|
||||
<ng-container *ngIf="showFolders">
|
||||
<h3 class="d-flex">
|
||||
{{ "folders" | i18n }}
|
||||
<a
|
||||
href="#"
|
||||
class="text-muted ml-auto"
|
||||
appStopClick
|
||||
(click)="addFolder()"
|
||||
appA11yTitle="{{ 'addFolder' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</h3>
|
||||
<ul class="bwi-ul card-ul">
|
||||
<ng-template #recursiveFolders let-folders>
|
||||
<li
|
||||
*ngFor="let f of folders"
|
||||
[ngClass]="{ active: selectedFolder && f.node.id === selectedFolderId }"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<i
|
||||
*ngIf="f.children.length"
|
||||
class="bwi-li bwi"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(f.node),
|
||||
'bwi-angle-down': !isCollapsed(f.node)
|
||||
}"
|
||||
(click)="collapse(f.node)"
|
||||
></i>
|
||||
<a href="#" class="text-break" appStopClick (click)="selectFolder(f.node)">
|
||||
<i
|
||||
*ngIf="f.children.length === 0"
|
||||
class="bwi bwi-li bwi-folder"
|
||||
aria-hidden="true"
|
||||
></i
|
||||
>{{ f.node.name }}
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="text-muted ml-auto show-active"
|
||||
appStopClick
|
||||
(click)="editFolder(f.node)"
|
||||
appA11yTitle="{{ 'editFolder' | i18n }}"
|
||||
*ngIf="f.node.id"
|
||||
>
|
||||
<i class="bwi bwi-pencil bwi-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="bwi-ul card-ul carets" *ngIf="f.children.length && !isCollapsed(f.node)">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }"
|
||||
>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-template>
|
||||
<ng-container *ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }">
|
||||
</ng-container>
|
||||
</ul>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showCollections && collections && collections.length">
|
||||
<h3>{{ "collections" | i18n }}</h3>
|
||||
<ul class="bwi-ul card-ul">
|
||||
<ng-template #recursiveCollections let-collections>
|
||||
<li
|
||||
*ngFor="let c of collections"
|
||||
[ngClass]="{ active: c.node.id === selectedCollectionId }"
|
||||
>
|
||||
<i
|
||||
*ngIf="c.children.length"
|
||||
class="bwi-li bwi"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(c.node),
|
||||
'bwi-angle-down': !isCollapsed(c.node)
|
||||
}"
|
||||
(click)="collapse(c.node)"
|
||||
></i>
|
||||
<a href="#" class="text-break" appStopClick (click)="selectCollection(c.node)">
|
||||
<i
|
||||
*ngIf="c.children.length === 0"
|
||||
class="bwi bwi-li bwi-collection"
|
||||
aria-hidden="true"
|
||||
></i
|
||||
>{{ c.node.name }}
|
||||
</a>
|
||||
<ul class="bwi-ul card-ul carets" *ngIf="c.children.length && !isCollapsed(c.node)">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"
|
||||
>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-template>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="recursiveCollections; context: { $implicit: nestedCollections }"
|
||||
>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
@ -1,29 +0,0 @@
|
||||
import { Component, EventEmitter, Output } from "@angular/core";
|
||||
|
||||
import { GroupingsComponent as BaseGroupingsComponent } from "jslib-angular/components/groupings.component";
|
||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-vault-groupings",
|
||||
templateUrl: "groupings.component.html",
|
||||
})
|
||||
export class GroupingsComponent extends BaseGroupingsComponent {
|
||||
@Output() onSearchTextChanged = new EventEmitter<string>();
|
||||
|
||||
searchText = "";
|
||||
searchPlaceholder: string = null;
|
||||
|
||||
constructor(
|
||||
collectionService: CollectionService,
|
||||
folderService: FolderService,
|
||||
stateService: StateService
|
||||
) {
|
||||
super(collectionService, folderService, stateService);
|
||||
}
|
||||
|
||||
searchTextChanged() {
|
||||
this.onSearchTextChanged.emit(this.searchText);
|
||||
}
|
||||
}
|
@ -421,12 +421,24 @@
|
||||
"message": "Copy URI",
|
||||
"description": "Copy URI to clipboard"
|
||||
},
|
||||
"me": {
|
||||
"message": "Me"
|
||||
},
|
||||
"myVault": {
|
||||
"message": "My Vault"
|
||||
},
|
||||
"allVaults": {
|
||||
"message": "All Vaults"
|
||||
},
|
||||
"vault": {
|
||||
"message": "Vault"
|
||||
},
|
||||
"vaults": {
|
||||
"message": "Vaults"
|
||||
},
|
||||
"vaultItems": {
|
||||
"message": "Vault Items"
|
||||
},
|
||||
"moveSelectedToOrg": {
|
||||
"message": "Move Selected to Organization"
|
||||
},
|
||||
@ -1124,11 +1136,14 @@
|
||||
"options": {
|
||||
"message": "Options"
|
||||
},
|
||||
"optionsDesc": {
|
||||
"preferences": {
|
||||
"message": "Preferences"
|
||||
},
|
||||
"preferencesDesc": {
|
||||
"message": "Customize your web vault experience."
|
||||
},
|
||||
"optionsUpdated": {
|
||||
"message": "Options updated"
|
||||
"preferencesUpdated": {
|
||||
"message": "Preferences updated"
|
||||
},
|
||||
"language": {
|
||||
"message": "Language"
|
||||
@ -4851,9 +4866,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"accessDenied": {
|
||||
"message": "Access Denied. You do not have permission to view this page."
|
||||
},
|
||||
"masterPassword": {
|
||||
"message": "Master Password"
|
||||
},
|
||||
"security": {
|
||||
"message": "Security"
|
||||
},
|
||||
"keys": {
|
||||
"message": "Keys"
|
||||
},
|
||||
"billingHistory": {
|
||||
"message": "Billing History"
|
||||
},
|
||||
"backToReports": {
|
||||
"message": "Back to Reports"
|
||||
},
|
||||
"organizationPicker": {
|
||||
"message": "Organization picker"
|
||||
},
|
||||
"currentOrganization": {
|
||||
"message": "Current organization",
|
||||
"description": "This is used by screen readers to indicate the organization that is currently being shown to the user."
|
||||
},
|
||||
"accountSettings": {
|
||||
"message": "Account Settings"
|
||||
},
|
||||
"generator": {
|
||||
"message": "Generator"
|
||||
},
|
||||
|
@ -39,12 +39,10 @@ body {
|
||||
}
|
||||
|
||||
.page-header,
|
||||
.secondary-header {
|
||||
.secondary-header,
|
||||
.tabbed-header {
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.6rem;
|
||||
@include themify($themes) {
|
||||
border-bottom: 1px solid themed("separator");
|
||||
}
|
||||
|
||||
&:not(.text-danger) {
|
||||
h1,
|
||||
@ -59,11 +57,22 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.page-header,
|
||||
.secondary-header {
|
||||
@include themify($themes) {
|
||||
border-bottom: 1px solid themed("separator");
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-header,
|
||||
.spaced-header {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
.tabbed-header {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
img.logo {
|
||||
display: block;
|
||||
height: 43px;
|
||||
|
@ -186,6 +186,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
button.no-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
@include themify($themes) {
|
||||
background-color: themed("badgePrimaryBackground");
|
||||
|
@ -71,6 +71,10 @@
|
||||
margin-left: 0.85em;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-margin {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-org-plans {
|
||||
|
@ -11,30 +11,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
max-width: 300px;
|
||||
min-width: 200px;
|
||||
|
||||
.dropdown-item-text {
|
||||
line-height: 1.3;
|
||||
@include themify($themes) {
|
||||
color: themed("dropdownTextColor");
|
||||
}
|
||||
|
||||
span,
|
||||
small {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
&.text-muted {
|
||||
@include themify($themes) {
|
||||
color: themed("dropdownTextMuted") !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.nav-item {
|
||||
> .nav-link {
|
||||
@include themify($themes) {
|
||||
@ -74,6 +50,29 @@
|
||||
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;
|
||||
|
||||
@ -90,6 +89,7 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -101,15 +101,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.org-name {
|
||||
line-height: 1;
|
||||
span {
|
||||
display: block;
|
||||
font-size: $font-size-lg;
|
||||
@include themify($themes) {
|
||||
color: themed("textHeadingColor");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user