1
0
mirror of https://github.com/bitwarden/web synced 2025-12-31 23:53:13 +00:00

[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>
This commit is contained in:
Robyn MacCallum
2022-05-03 09:00:15 -04:00
committed by GitHub
parent a65a0116cd
commit da683e0df4
45 changed files with 3342 additions and 4656 deletions

2
.gitmodules vendored
View File

@@ -1,4 +1,4 @@
[submodule "jslib"]
path = jslib
url = https://github.com/bitwarden/jslib.git
branch = master
branch = feature/endUserVaultRefresh

4855
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -0,0 +1,483 @@
import { NgModule } from "@angular/core";
import { VerifyMasterPasswordComponent } from "jslib-angular/components/verify-master-password.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 { 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 { OrganizationsComponent } from "../settings/organizations.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 { 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 { ShareComponent } from "../vault/share.component";
import { SharedModule } from "./shared.module";
import { VaultFilterModule } from "./vault-filter/vault-filter.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],
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,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationsComponent,
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,
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,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingComponent,
UserLayoutComponent,
UserSubscriptionComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyMasterPasswordComponent,
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,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationsComponent,
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,
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,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingComponent,
UserLayoutComponent,
UserSubscriptionComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyMasterPasswordComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
],
})
export class LooseComponentsModule {}

View 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 {}

View File

@@ -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>

View File

@@ -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 {}

View File

@@ -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>

View File

@@ -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 {}

View File

@@ -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 }"
>
&nbsp;{{ 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 }"
>
&nbsp;{{ 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>

View File

@@ -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";
}

View File

@@ -0,0 +1,35 @@
<div 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>

View File

@@ -0,0 +1,180 @@
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 { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { PolicyType } from "jslib-common/enums/policyType";
import { Utils } from "jslib-common/misc/utils";
import { Organization } from "jslib-common/models/domain/organization";
import { Policy } from "jslib-common/models/domain/policy";
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
@Component({
selector: "app-organization-options",
templateUrl: "organization-options.component.html",
})
export class OrganizationOptionsComponent {
actionPromise: Promise<any>;
policies: Policy[];
loaded = false;
@Input() organization: Organization;
constructor(
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private apiService: ApiService,
private syncService: SyncService,
private cryptoService: CryptoService,
private policyService: PolicyService,
private logService: LogService
) {}
async ngOnInit() {
await this.syncService.fullSync(true);
await this.load();
}
async load() {
this.policies = await this.policyService.getAll(PolicyType.ResetPassword);
this.loaded = true;
}
allowEnrollmentChanges(org: Organization): boolean {
if (org.usePolicies && org.useResetPassword && org.hasPublicAndPrivateKeys) {
const policy = this.policies.find((p) => p.organizationId === org.id);
if (policy != null && policy.enabled) {
return org.resetPasswordEnrolled && policy.data.autoEnrollEnabled ? false : true;
}
}
return false;
}
showEnrolledStatus(org: Organization): boolean {
return (
org.useResetPassword &&
org.resetPasswordEnrolled &&
this.policies.some((p) => p.organizationId === org.id && p.enabled)
);
}
async unlinkSso(org: Organization) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("unlinkSsoConfirmation"),
org.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.apiService.deleteSsoUser(org.id).then(() => {
return this.syncService.fullSync(true);
});
await this.actionPromise;
this.platformUtilsService.showToast("success", null, "Unlinked SSO");
await this.load();
} catch (e) {
this.logService.error(e);
}
}
async leave(org: Organization) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("leaveOrganizationConfirmation"),
org.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.apiService.postLeaveOrganization(org.id).then(() => {
return this.syncService.fullSync(true);
});
await this.actionPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
await this.load();
} catch (e) {
this.logService.error(e);
}
}
async toggleResetPasswordEnrollment(org: Organization) {
// Set variables
let keyString: string = null;
let toastStringRef = "withdrawPasswordResetSuccess";
// Enrolling
if (!org.resetPasswordEnrolled) {
// Alert user about enrollment
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("resetPasswordEnrollmentWarning"),
null,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return;
}
// Retrieve Public Key
this.actionPromise = this.apiService
.getOrganizationKeys(org.id)
.then(async (response) => {
if (response == null) {
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
}
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
keyString = encryptedKey.encryptedString;
toastStringRef = "enrollPasswordResetSuccess";
// Create request and execute enrollment
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.resetPasswordKey = keyString;
return this.apiService.putOrganizationUserResetPasswordEnrollment(
org.id,
org.userId,
request
);
})
.then(() => {
return this.syncService.fullSync(true);
});
} else {
// Withdrawal
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.resetPasswordKey = keyString;
this.actionPromise = this.apiService
.putOrganizationUserResetPasswordEnrollment(org.id, org.userId, request)
.then(() => {
return this.syncService.fullSync(true);
});
}
try {
await this.actionPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t(toastStringRef));
await this.load();
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -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>&nbsp;{{ "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>&nbsp;{{ "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>&nbsp;{{ "trash" | i18n }}
</button>
</span>
</li>
</ul>
</ng-container>

View File

@@ -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 {}

View File

@@ -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>

View File

@@ -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 {}

View 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>

View 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();
}
}

View 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 {}

View File

@@ -0,0 +1,3 @@
import { VaultFilterService as BaseVaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
export class VaultFilterService extends BaseVaultFilterService {}

View File

@@ -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 {}

View File

@@ -1,18 +1,20 @@
<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 [ngClass]="{ 'col-6': isShowingCards, 'col-9': !isShowingCards }">
<div class="page-header d-flex">

View File

@@ -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 { CryptoService } from "jslib-common/abstractions/crypto.service";
@@ -23,24 +24,24 @@ import { TokenService } from "jslib-common/abstractions/token.service";
import { CipherType } from "jslib-common/enums/cipherType";
import { CipherView } from "jslib-common/models/view/cipherView";
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("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@@ -58,12 +59,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;
deleted = false;
trashCleanupWarning: string = null;
activeFilter: VaultFilter = new VaultFilter();
constructor(
private syncService: SyncService,
@@ -79,7 +83,8 @@ export class VaultComponent implements OnInit, OnDestroy {
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private stateService: StateService,
private organizationService: OrganizationService
private organizationService: OrganizationService,
private vaultService: VaultService
) {}
async ngOnInit() {
@@ -93,40 +98,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();
await this.groupingsComponent.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.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
@@ -134,7 +123,8 @@ export class VaultComponent implements OnInit, OnDestroy {
case "syncCompleted":
if (message.successfully) {
await Promise.all([
this.groupingsComponent.load(),
this.filterComponent.reloadCollectionsAndFolders(this.activeFilter),
this.filterComponent.reloadOrganizations(),
this.ciphersComponent.load(this.ciphersComponent.filter),
]);
this.changeDetectorRef.detectChanges();
@@ -159,60 +149,13 @@ export class VaultComponent implements OnInit, OnDestroy {
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();
}
@@ -221,6 +164,40 @@ export class VaultComponent implements OnInit, OnDestroy {
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) {
@@ -292,7 +269,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);
});
}
);
@@ -306,13 +283,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);
});
}
);
@@ -322,15 +297,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) {
@@ -366,14 +347,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 = {

View File

@@ -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 {}

View File

@@ -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 {}

View File

@@ -1,17 +1,21 @@
<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">

View File

@@ -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 { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -21,22 +22,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;
@@ -52,6 +53,7 @@ export class VaultComponent implements OnInit, OnDestroy {
type: CipherType = null;
deleted = false;
trashCleanupWarning: string = null;
activeFilter: VaultFilter = new VaultFilter();
constructor(
private route: ActivatedRoute,
@@ -64,7 +66,8 @@ export class VaultComponent implements OnInit, OnDestroy {
private messagingService: MessagingService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService
private platformUtilsService: PlatformUtilsService,
private vaultService: VaultService
) {}
ngOnInit() {
@@ -75,11 +78,11 @@ export class VaultComponent implements OnInit, OnDestroy {
);
this.route.parent.params.pipe(first()).subscribe(async (params) => {
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) => {
@@ -88,7 +91,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();
@@ -98,27 +105,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);
@@ -134,63 +124,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) {
@@ -232,7 +206,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;
@@ -249,7 +225,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];
@@ -286,7 +264,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

View File

@@ -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 {}

View 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 {}

View 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";
}
}

View File

@@ -26,7 +26,6 @@ import { ReusedPasswordsReportComponent } from "./tools/reused-passwords-report.
import { ToolsComponent } from "./tools/tools.component";
import { UnsecuredWebsitesReportComponent } from "./tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./tools/weak-passwords-report.component";
import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [
{
@@ -38,7 +37,12 @@ const routes: Routes = [
},
children: [
{ path: "", pathMatch: "full", redirectTo: "vault" },
{ path: "vault", component: VaultComponent, data: { titleId: "vault" } },
{
path: "vault",
loadChildren: async () =>
(await import("../modules/vault/modules/organization-vault/organization-vault.module"))
.OrganizationVaultModule,
},
{
path: "tools",
component: ToolsComponent,

View File

@@ -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_");
}
}

View File

@@ -42,7 +42,6 @@ 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 = [
{
@@ -139,7 +138,12 @@ const routes: Routes = [
component: UserLayoutComponent,
canActivate: [AuthGuard],
children: [
{ path: "vault", component: VaultComponent, data: { titleId: "vaults" } },
{
path: "vault",
loadChildren: async () =>
(await import("./modules/vault/modules/individual-vault/individual-vault.module"))
.IndividualVaultModule,
},
{ path: "sends", component: SendComponent, data: { title: "Send" } },
{
path: "settings",

View File

@@ -1,453 +1,25 @@
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 { VerifyMasterPasswordComponent } from "jslib-angular/components/verify-master-password.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 { 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 { 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 { OrganizationPlansComponent } from "./settings/organization-plans.component";
import { OrganizationsComponent } from "./settings/organizations.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 { 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 { SharedModule } from "./modules/shared.module";
import { VaultFilterModule } from "./modules/vault-filter/vault-filter.module";
import { IndividualVaultModule } from "./modules/vault/modules/individual-vault/individual-vault.module";
import { OrganizationVaultModule } from "./modules/vault/modules/organization-vault/organization-vault.module";
@NgModule({
imports: [
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
BadgeModule,
ButtonModule,
CalloutModule,
ToastrModule,
MenuModule,
SharedModule,
LooseComponentsModule,
IndividualVaultModule,
OrganizationVaultModule,
VaultFilterModule,
],
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,
GroupingsComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
LinkSsoComponent,
LockComponent,
LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
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,
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,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingComponent,
UserLayoutComponent,
UserSubscriptionComponent,
VaultComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyMasterPasswordComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
exports: [
LooseComponentsModule,
IndividualVaultModule,
OrganizationVaultModule,
VaultFilterModule,
],
exports: [FooterComponent, NavbarComponent, OrganizationPlansComponent],
providers: [DatePipe],
bootstrap: [],
})
export class OssModule {}

View File

@@ -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>

View File

@@ -47,7 +47,9 @@ export class UserBillingComponent implements OnInit {
if (this.organizationId != null) {
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
} else {
this.billing = await this.apiService.getUserBilling();
// let history = await this.apiService.getUserBillingHistory();
// let payment = await this.apiService.getUserBillingPayment();
this.billing = new BillingResponse(null);
}
this.loading = false;
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -424,6 +424,9 @@
"myVault": {
"message": "My Vault"
},
"allVaults": {
"message": "All Vaults"
},
"vault": {
"message": "Vault"
},

View File

@@ -186,6 +186,20 @@
}
}
button.no-btn {
background: transparent;
border: none;
padding: 0;
color: inherit;
&:hover,
&:focus {
background: transparent;
box-shadow: none;
color: inherit;
}
}
.badge-primary {
@include themify($themes) {
background-color: themed("badgePrimaryBackground");

View File

@@ -71,6 +71,10 @@
margin-left: 0.85em;
}
}
&.no-margin {
margin-left: 0;
}
}
.card-org-plans {

View File

@@ -26,89 +26,6 @@
}
}
app-vault-groupings,
app-org-vault-groupings,
.groupings {
.card {
#search {
margin-bottom: 1rem;
@include themify($themes) {
background-color: themed("inputBackgroundColor");
border-color: themed("inputBorderColor");
color: themed("inputTextColor");
}
&::placeholder {
@include themify($themes) {
color: themed("inputPlaceholderColor");
}
}
}
h3 {
font-weight: normal;
text-transform: uppercase;
@include themify($themes) {
color: themed("textMuted");
}
}
ul:last-child {
margin-bottom: 0;
}
.card-body a {
@include themify($themes) {
color: themed("textHeadingColor");
font-weight: themed("linkWeight");
}
&:hover {
&.text-muted {
@include themify($themes) {
color: themed("iconHover") !important;
}
}
}
}
.show-active {
display: none;
}
li {
> .bwi,
> div > .bwi {
cursor: pointer;
}
}
li.active {
> .show-active,
> div .show-active {
display: inline;
}
}
li.active {
> a:first-of-type,
> div a:first-of-type {
font-weight: bold;
@include themify($themes) {
color: themed("linkColor");
}
}
> .bwi,
> div > .bwi {
@include themify($themes) {
color: themed("linkColor");
}
}
}
}
}
app-generator {
#lengthRange {
width: 100%;
@@ -260,3 +177,54 @@ app-sponsored-families {
}
}
}
.collapsable-row {
display: flex;
padding-top: 15px;
i {
margin-top: 3px;
}
.filter-title {
padding-left: 5px;
}
&.active {
@include themify($themes) {
color: themed("primary");
}
}
}
.vault-filter-option {
padding-bottom: 3px;
&.active {
@include themify($themes) {
color: themed("primary");
font-weight: bold;
}
}
button.org-options {
background: none;
border: none;
padding: 0;
&:hover,
&:focus {
@include themify($themes) {
color: themed("iconHover") !important;
box-shadow: none;
}
}
}
}
.org-filter-heading {
@include themify($themes) {
color: themed("textColor");
}
&.active {
@include themify($themes) {
color: themed("primary");
font-weight: bold;
}
}
}

View File

@@ -57,3 +57,4 @@
@import "./plugins";
@import "./tables";
@import "./toasts";
@import "./vault-filters";

203
src/scss/vault-filters.scss Normal file
View File

@@ -0,0 +1,203 @@
.filter {
padding-bottom: 10px;
}
.filter-heading {
display: flex;
text-transform: uppercase;
align-items: center;
* {
@include themify($themes) {
color: themed("headingColor");
}
font-size: $font-size-base;
}
a.create-organization-link {
&:hover {
@include themify($themes) {
color: themed("iconHover") !important;
}
}
}
button {
@extend .no-btn;
}
h3,
button.filter-button {
text-transform: uppercase;
text-transform: uppercase;
margin: 0;
@include themify($themes) {
color: themed("textMuted");
}
}
button.filter-button {
&:hover,
&:focus,
&.active {
@include themify($themes) {
color: themed("linkColor") !important;
}
}
&.active {
font-weight: bold;
}
}
button.toggle-button {
&:hover,
&:focus {
@include themify($themes) {
color: themed("iconHover") !important;
}
}
}
}
.filter-options {
padding: 0;
list-style: none;
margin: 0 0 15px 0;
.nested-filter-options {
list-style: none;
margin-bottom: 0px;
padding-left: 0.85em;
}
}
.filter-option {
@include themify($themes) {
color: themed("textColor");
}
&.active {
> .filter-buttons {
.filter-button {
@include themify($themes) {
color: themed("linkColor") !important;
}
font-weight: bold;
}
.edit-button {
visibility: visible;
}
}
}
}
.filter-buttons {
display: flex;
align-items: center;
button {
@extend .no-btn;
}
.toggle-button {
&:hover,
&:focus {
@include themify($themes) {
color: themed("iconHover") !important;
}
}
}
.filter-button {
&:hover,
&:focus,
&:active {
@include themify($themes) {
color: themed("linkColor") !important;
}
}
}
.edit-button {
visibility: hidden;
margin-left: auto;
@include themify($themes) {
color: themed("headingButtonColor");
}
&:hover,
&:focus {
@include themify($themes) {
color: themed("iconHover") !important;
}
}
}
}
.groupings {
.card {
#search {
margin-bottom: 1rem;
@include themify($themes) {
background-color: themed("inputBackgroundColor");
border-color: themed("inputBorderColor");
color: themed("inputTextColor");
}
&::placeholder {
@include themify($themes) {
color: themed("inputPlaceholderColor");
}
}
}
h3 {
font-weight: normal;
text-transform: uppercase;
@include themify($themes) {
color: themed("textMuted");
}
}
ul:last-child {
margin-bottom: 0;
}
.card-body a {
@include themify($themes) {
color: themed("textHeadingColor");
font-weight: themed("linkWeight");
}
&:hover {
&.text-muted {
}
}
}
.show-active {
display: none;
}
li.active {
> .show-active,
> div .show-active {
display: inline;
}
}
li.active {
> button:first-of-type,
> div button:first-of-type {
@include themify($themes) {
color: themed("linkColor");
}
}
> .bwi,
> div > .bwi {
@include themify($themes) {
color: themed("linkColor");
}
}
}
}
}