mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
10 Commits
feat/expan
...
feature/cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
283f295481 | ||
|
|
749ead5863 | ||
|
|
8d369bcf84 | ||
|
|
aa7fb1befb | ||
|
|
43ca31e0ae | ||
|
|
0dfc8c5a0b | ||
|
|
7b55c8ad1a | ||
|
|
c1801dfc61 | ||
|
|
59a53740a4 | ||
|
|
dd2c2b9720 |
2
jslib
2
jslib
Submodule jslib updated: 65584c6496...3bf25edd3e
@@ -53,7 +53,13 @@ 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 {
|
||||
BadgeModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
FormFieldModule,
|
||||
MenuModule,
|
||||
} from "@bitwarden/components";
|
||||
import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
||||
import { ToastrModule } from "ngx-toastr";
|
||||
|
||||
@@ -126,6 +132,7 @@ registerLocaleData(localeZhTw, "zh-TW");
|
||||
BadgeModule,
|
||||
ButtonModule,
|
||||
MenuModule,
|
||||
FormFieldModule,
|
||||
],
|
||||
exports: [
|
||||
CommonModule,
|
||||
@@ -136,6 +143,7 @@ registerLocaleData(localeZhTw, "zh-TW");
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
BadgeModule,
|
||||
FormFieldModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
ToastrModule,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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",
|
||||
@@ -21,17 +20,10 @@ export class VaultFilterComponent extends BaseVaultFilterComponent {
|
||||
|
||||
organization: Organization;
|
||||
|
||||
constructor(protected vaultFilterService: VaultFilterService) {
|
||||
constructor(vaultFilterService: VaultFilterService) {
|
||||
super(vaultFilterService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
this.vaultFilterService.collapsedFilterNodes$.subscribe((nodes) => {
|
||||
this.collapsedFilterNodes = nodes;
|
||||
});
|
||||
}
|
||||
|
||||
searchTextChanged() {
|
||||
this.onSearchTextChanged.emit(this.searchText);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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";
|
||||
@@ -16,7 +17,6 @@ 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],
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
export class VaultFilterService extends BaseVaultFilterService {}
|
||||
|
||||
@@ -34,7 +34,6 @@ 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";
|
||||
@@ -89,8 +88,7 @@ export class IndividualVaultComponent implements OnInit, OnDestroy {
|
||||
private organizationService: OrganizationService,
|
||||
private vaultService: VaultService,
|
||||
private cipherService: CipherService,
|
||||
private passwordRepromptService: PasswordRepromptService,
|
||||
private vaultFilterService: VaultFilterService
|
||||
private passwordRepromptService: PasswordRepromptService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -189,7 +187,6 @@ export class IndividualVaultComponent implements OnInit, OnDestroy {
|
||||
this.activeFilter.myVaultOnly = true;
|
||||
} else {
|
||||
this.activeFilter.selectedOrganizationId = orgId;
|
||||
await this.vaultFilterService.ensureVaultFiltersAreExpanded();
|
||||
}
|
||||
await this.applyVaultFilter(this.activeFilter);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } 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) {
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
// TODO: We need to fix this issue once and for all.
|
||||
if ((await this.syncService.getLastSync()) == null) {
|
||||
await this.syncService.fullSync(false);
|
||||
@@ -39,6 +39,16 @@ 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(["/"]);
|
||||
}
|
||||
|
||||
@@ -14,62 +14,50 @@
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
[formGroup]="formData"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="name">{{ "organizationName" | i18n }}</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Name"
|
||||
[(ngModel)]="org.name"
|
||||
[disabled]="selfHosted"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
|
||||
<input
|
||||
id="billingEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="BillingEmail"
|
||||
[(ngModel)]="org.billingEmail"
|
||||
[disabled]="selfHosted || !canManageBilling"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="businessName">{{ "businessName" | i18n }}</label>
|
||||
<input
|
||||
id="businessName"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="BusinessName"
|
||||
[(ngModel)]="org.businessName"
|
||||
[disabled]="selfHosted || !canManageBilling"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="identifier">{{ "identifier" | i18n }}</label>
|
||||
<input
|
||||
id="identifier"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Identifier"
|
||||
[(ngModel)]="org.identifier"
|
||||
/>
|
||||
</div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "organizationName" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="name" />
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "billingEmail" | i18n }}</bit-label>
|
||||
<input bitInput type="email" formControlName="billingEmail" />
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "businessName" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="businessName" />
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "identifier" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="identifier" />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<app-avatar data="{{ org.name }}" dynamic="true" size="75" fontSize="35"></app-avatar>
|
||||
<app-avatar
|
||||
[data]="formData.get('name').value"
|
||||
dynamic="true"
|
||||
size="75"
|
||||
fontSize="35"
|
||||
></app-avatar>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<button
|
||||
type="submit"
|
||||
bit-button
|
||||
buttonType="primary"
|
||||
class="btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formData"></bit-error-summary>
|
||||
</form>
|
||||
<ng-container *ngIf="canUseApi">
|
||||
<div class="secondary-header border-0 mb-0">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
@@ -41,10 +42,19 @@ export class AccountComponent {
|
||||
org: OrganizationResponse;
|
||||
formPromise: Promise<any>;
|
||||
taxFormPromise: Promise<any>;
|
||||
showErrorSummary = false;
|
||||
|
||||
private organizationId: string;
|
||||
|
||||
formData = this.formBuilder.group({
|
||||
name: ["", [Validators.required]],
|
||||
billingEmail: ["", [Validators.required, Validators.email]],
|
||||
businessName: [],
|
||||
identifier: [],
|
||||
});
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private modalService: ModalService,
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
@@ -60,6 +70,12 @@ export class AccountComponent {
|
||||
async ngOnInit() {
|
||||
this.selfHosted = this.platformUtilsService.isSelfHost();
|
||||
|
||||
if (this.selfHosted) {
|
||||
this.formData.disable();
|
||||
} else {
|
||||
this.formData.enable();
|
||||
}
|
||||
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
this.canManageBilling = (
|
||||
@@ -68,6 +84,13 @@ export class AccountComponent {
|
||||
try {
|
||||
this.org = await this.apiService.getOrganization(this.organizationId);
|
||||
this.canUseApi = this.org.useApi;
|
||||
|
||||
this.formData.setValue({
|
||||
name: this.org.name,
|
||||
billingEmail: this.org.billingEmail,
|
||||
businessName: this.org.businessName,
|
||||
identifier: this.org.identifier,
|
||||
});
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
@@ -76,12 +99,19 @@ export class AccountComponent {
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.formData.markAllAsTouched();
|
||||
this.showErrorSummary = true;
|
||||
|
||||
if (!this.formData.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const request = new OrganizationUpdateRequest();
|
||||
request.name = this.org.name;
|
||||
request.businessName = this.org.businessName;
|
||||
request.billingEmail = this.org.billingEmail;
|
||||
request.identifier = this.org.identifier;
|
||||
request.name = this.formData.get("name").value;
|
||||
request.businessName = this.formData.get("businessName").value;
|
||||
request.billingEmail = this.formData.get("billingEmail").value;
|
||||
request.identifier = this.formData.get("identifier").value;
|
||||
|
||||
// Backfill pub/priv key if necessary
|
||||
if (!this.org.hasPublicAndPrivateKeys) {
|
||||
|
||||
@@ -5005,7 +5005,7 @@
|
||||
"message": "Service"
|
||||
},
|
||||
"unknownCipher": {
|
||||
"message": "Unknown Item, you may need to login with another account to access this item."
|
||||
"message": "Unknown Item, you may need to request permission to access this item."
|
||||
},
|
||||
"cannotSponsorSelf": {
|
||||
"message": "You cannot redeem for the active account. Enter a different email."
|
||||
@@ -5066,5 +5066,20 @@
|
||||
},
|
||||
"apiAccessToken": {
|
||||
"message": "API Access Token"
|
||||
},
|
||||
"inputRequired": {
|
||||
"message": "Input is required."
|
||||
},
|
||||
"inputEmail": {
|
||||
"message": "Input is not an email-address."
|
||||
},
|
||||
"fieldsNeedAttention": {
|
||||
"message": "$COUNT$ field(s) above need your attention.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user