1
0
mirror of https://github.com/bitwarden/web synced 2025-12-06 00:03:28 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
addison
3ec8b61efc [fix] Expose observable without write access to callers 2022-05-17 11:20:30 -04:00
addison
d7a4d3f538 [feat] Allow the org badge click event to expand collapsed vault filters
1. Extend the BaseVaultFiltersService to manage an observable for collapsed nodes that can be modified from outside the VaultFiltersComponent
2. Subscribe to the new observable in VaultFiltersComponent.ngOnInit() to update the collapsed state of filters when changed from outside the VaultFiltersComponent
3. Add a helper method to VaultFiltersService for expanding vault filters if they are collapsed that sends a new value through the subscription
4. Wire everything up in the IndividualVaultComponent so applying a not null organization filter expanded the vault filters
2022-05-17 10:53:16 -04:00
15 changed files with 98 additions and 47 deletions

2
jslib

Submodule jslib updated: 3cb94623e2...65584c6496

View File

@@ -122,20 +122,17 @@ export abstract class BaseEventsComponent {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = await this.eventService.getEventInfo(r);
const user = this.getUserName(r, userId);
const userName = user != null ? user.name : this.i18nService.t("unknown");
return new EventView({
message: eventInfo.message,
humanReadableMessage: eventInfo.humanReadableMessage,
appIcon: eventInfo.appIcon,
appName: eventInfo.appName,
userId: userId,
userName: r.installationId != null ? `Installation: ${r.installationId}` : userName,
userName: user != null ? user.name : this.i18nService.t("unknown"),
userEmail: user != null ? user.email : "",
date: r.date,
ip: r.ipAddress,
type: r.type,
installationId: r.installationId,
});
})
);

View File

@@ -1,6 +1,6 @@
<div class="container footer text-muted">
<div class="row">
<div class="col">&copy; {{ year }} Bitwarden Inc.</div>
<div class="col">&copy; {{ year }}, Bitwarden Inc.</div>
<div class="col text-center"></div>
<div class="col text-right">
{{ "versionNumber" | i18n: version }}

View File

@@ -1,5 +1,5 @@
<router-outlet></router-outlet>
<div class="container my-5 text-muted text-center">
&copy; {{ year }} Bitwarden Inc. <br />
&copy; {{ year }}, Bitwarden Inc. <br />
{{ "versionNumber" | i18n: version }}
</div>

View File

@@ -16,7 +16,7 @@
aria-hidden="true"
></i>
</button>
<h3 class="filter-title">&nbsp;{{ collectionsGrouping.name | i18n }}</h3>
<h3 class="filter-title">{{ collectionsGrouping.name | i18n }}</h3>
</div>
<ul id="collection-filters" *ngIf="!isCollapsed(collectionsGrouping)" class="filter-options">
<ng-template #recursiveCollections let-collections>
@@ -51,7 +51,7 @@
class="bwi bwi-collection bwi-fw"
aria-hidden="true"
></i
>&nbsp;{{ c.node.name }}
>{{ c.node.name }}
</button>
</span>
<ul

View File

@@ -16,7 +16,9 @@
}"
></i>
</button>
<h3 class="filter-title">&nbsp;{{ "folders" | i18n }}</h3>
<h3 class="filter-title">
{{ "folders" | i18n }}
</h3>
<button
class="text-muted ml-auto add-button"
(click)="addFolder()"
@@ -54,7 +56,7 @@
</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
>&nbsp;{{ f.node.name }}
>{{ f.node.name }}
</button>
<button
class="edit-button"

View File

@@ -15,7 +15,9 @@
}"
></i>
</button>
<h3>&nbsp;{{ "types" | i18n }}</h3>
<h3>
{{ "types" | i18n }}
</h3>
</div>
<ul id="type-filters" *ngIf="!isCollapsed" class="filter-options">
<li
@@ -24,14 +26,14 @@
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Login)">
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>&nbsp;{{ "typeLogin" | i18n }}
<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>&nbsp;{{ "typeCard" | i18n }}
<i class="bwi bwi-fw bwi-credit-card" aria-hidden="true"></i>{{ "typeCard" | i18n }}
</button>
</span>
</li>
@@ -41,7 +43,7 @@
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Identity)">
<i class="bwi bwi-fw bwi-id-card" aria-hidden="true"></i>&nbsp;{{ "typeIdentity" | i18n }}
<i class="bwi bwi-fw bwi-id-card" aria-hidden="true"></i>{{ "typeIdentity" | i18n }}
</button>
</span>
</li>
@@ -51,9 +53,7 @@
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.SecureNote)">
<i class="bwi bwi-fw bwi-sticky-note" aria-hidden="true"></i>&nbsp;{{
"typeSecureNote" | i18n
}}
<i class="bwi bwi-fw bwi-sticky-note" aria-hidden="true"></i>{{ "typeSecureNote" | i18n }}
</button>
</span>
</li>

View File

@@ -1,9 +1,10 @@
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";
import { VaultFilterService } from "./vault-filter.service";
@Component({
selector: "app-vault-filter",
templateUrl: "vault-filter.component.html",
@@ -20,11 +21,32 @@ export class VaultFilterComponent extends BaseVaultFilterComponent {
organization: Organization;
constructor(vaultFilterService: VaultFilterService) {
constructor(protected vaultFilterService: VaultFilterService) {
super(vaultFilterService);
}
async ngOnInit() {
await super.ngOnInit();
this.vaultFilterService.collapsedFilterNodes$.subscribe((nodes) => {
this.collapsedFilterNodes = nodes;
});
}
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();
this.activePersonalOwnershipPolicy =
await this.vaultFilterService.checkForPersonalOwnershipPolicy();
this.activeSingleOrganizationPolicy =
await this.vaultFilterService.checkForSingleOrganizationPolicy();
}
async initCollections() {
return await this.vaultFilterService.buildCollections(this.organization?.id);
}
}

View File

@@ -1,6 +1,11 @@
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";
@@ -11,6 +16,7 @@ import { OrganizationOptionsComponent } from "./components/organization-options.
import { StatusFilterComponent } from "./components/status-filter.component";
import { TypeFilterComponent } from "./components/type-filter.component";
import { VaultFilterComponent } from "./vault-filter.component";
import { VaultFilterService } from "./vault-filter.service";
@NgModule({
imports: [SharedModule],
@@ -24,6 +30,19 @@ import { VaultFilterComponent } from "./vault-filter.component";
TypeFilterComponent,
],
exports: [VaultFilterComponent],
providers: [VaultFilterService],
providers: [
{
provide: VaultFilterService,
useClass: VaultFilterService,
deps: [
StateService,
OrganizationService,
FolderService,
CipherService,
CollectionService,
PolicyService,
],
},
],
})
export class VaultFilterModule {}

View File

@@ -0,0 +1,28 @@
import { BehaviorSubject, Observable } from "rxjs";
import { VaultFilterService as BaseVaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
export class VaultFilterService extends BaseVaultFilterService {
private _collapsedFilterNodes = new BehaviorSubject<Set<string>>(null);
collapsedFilterNodes$: Observable<Set<string>> = this._collapsedFilterNodes.asObservable();
async buildCollapsedFilterNodes(): Promise<Set<string>> {
const nodes = await super.buildCollapsedFilterNodes();
this._collapsedFilterNodes.next(nodes);
return nodes;
}
async storeCollapsedFilterNodes(collapsedFilterNodes: Set<string>): Promise<void> {
await super.storeCollapsedFilterNodes(collapsedFilterNodes);
this._collapsedFilterNodes.next(collapsedFilterNodes);
}
async ensureVaultFiltersAreExpanded() {
const collapsedFilterNodes = await super.buildCollapsedFilterNodes();
if (!collapsedFilterNodes.has("vaults")) {
return;
}
collapsedFilterNodes.delete("vaults");
await this.storeCollapsedFilterNodes(collapsedFilterNodes);
}
}

View File

@@ -34,6 +34,7 @@ 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 { VaultFilterService } from "../../../vault-filter/vault-filter.service";
import { VaultService } from "../../vault.service";
const BroadcasterSubscriptionId = "VaultComponent";
@@ -88,7 +89,8 @@ export class IndividualVaultComponent implements OnInit, OnDestroy {
private organizationService: OrganizationService,
private vaultService: VaultService,
private cipherService: CipherService,
private passwordRepromptService: PasswordRepromptService
private passwordRepromptService: PasswordRepromptService,
private vaultFilterService: VaultFilterService
) {}
async ngOnInit() {
@@ -187,6 +189,7 @@ export class IndividualVaultComponent implements OnInit, OnDestroy {
this.activeFilter.myVaultOnly = true;
} else {
this.activeFilter.selectedOrganizationId = orgId;
await this.vaultFilterService.ensureVaultFiltersAreExpanded();
}
await this.applyVaultFilter(this.activeFilter);
}

View File

@@ -98,7 +98,6 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
this.vaultFilterComponent.reloadCollectionsAndFolders(
new VaultFilter({
selectedOrganizationId: this.organization.id,
useAdminCollections: this.organization.canEditAnyCollection,
} as Partial<VaultFilter>)
),
this.ciphersComponent.refresh(),
@@ -111,10 +110,7 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
});
}
await this.vaultFilterComponent.reloadCollectionsAndFolders(
new VaultFilter({
selectedOrganizationId: this.organization.id,
useAdminCollections: this.organization.canEditAnyCollection,
} as Partial<VaultFilter>)
new VaultFilter({ selectedOrganizationId: this.organization.id } as Partial<VaultFilter>)
);
await this.ciphersComponent.reload();

View File

@@ -1,5 +1,5 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
@@ -17,7 +17,7 @@ export class PermissionsGuard implements CanActivate {
private syncService: SyncService
) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
async canActivate(route: ActivatedRouteSnapshot) {
// TODO: We need to fix this issue once and for all.
if ((await this.syncService.getLastSync()) == null) {
await this.syncService.fullSync(false);
@@ -39,16 +39,6 @@ export class PermissionsGuard implements CanActivate {
const permissions = route.data == null ? [] : (route.data.permissions as Permissions[]);
if (permissions != null && !org.hasAnyPermission(permissions)) {
// Handle linkable ciphers for organizations the user only has view access to
// https://bitwarden.atlassian.net/browse/EC-203
if (state.root.queryParamMap.has("cipherId")) {
return this.router.createUrlTree(["/vault"], {
queryParams: {
cipherId: state.root.queryParamMap.get("cipherId"),
},
});
}
this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied"));
return this.router.createUrlTree(["/"]);
}

View File

@@ -307,9 +307,6 @@ export class EventService {
case EventType.Organization_DisabledKeyConnector:
msg = humanReadableMsg = this.i18nService.t("disabledKeyConnector");
break;
case EventType.Organization_SponsorshipsSynced:
msg = humanReadableMsg = this.i18nService.t("sponsorshipsSynced");
break;
// Policies
case EventType.Policy_Updated: {
msg = this.i18nService.t("modifiedPolicyId", this.formatPolicyId(ev));

View File

@@ -5005,7 +5005,7 @@
"message": "Service"
},
"unknownCipher": {
"message": "Unknown Item, you may need to request permission to access this item."
"message": "Unknown Item, you may need to login with another account to access this item."
},
"cannotSponsorSelf": {
"message": "You cannot redeem for the active account. Enter a different email."
@@ -5041,9 +5041,6 @@
"message": "Last Sync",
"Description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\""
},
"sponsorshipsSynced": {
"message": "Self-hosted sponsorships synced."
},
"billingManagedByProvider": {
"message": "Managed by $PROVIDER$",
"placeholders": {