1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-22 03:03:43 +00:00

Merge master into merge/feature/org-admin-refresh (using imerge)

This commit is contained in:
Shane Melton
2022-12-13 07:31:22 -08:00
721 changed files with 18307 additions and 5660 deletions

View File

@@ -21,4 +21,4 @@
## Documentation
Please refer to the [Web vault section](https://contributing.bitwarden.com/clients/web-vault/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
Please refer to the [Web vault section](https://contributing.bitwarden.com/getting-started/clients/web-vault/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.

View File

@@ -16,7 +16,6 @@
"proxyEvents": "https://events.bitwarden.com"
},
"flags": {
"showTrial": true,
"showPasswordless": false
}
}

View File

@@ -10,7 +10,7 @@
"proxyNotifications": "http://localhost:61840"
},
"flags": {
"showTrial": true,
"secretsManager": true,
"showPasswordless": true
}
}

View File

@@ -6,7 +6,5 @@
"proxyNotifications": "http://localhost:61841",
"port": 8081
},
"flags": {
"showTrial": false
}
"flags": {}
}

View File

@@ -10,7 +10,7 @@
"proxyEvents": "https://events.qa.bitwarden.pw"
},
"flags": {
"showTrial": true,
"secretsManager": false,
"showPasswordless": true
}
}

View File

@@ -7,7 +7,6 @@
"port": 8081
},
"flags": {
"showTrial": false,
"showPasswordless": false
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
"version": "2022.10.2",
"version": "2022.11.2",
"scripts": {
"build:oss": "webpack",
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",

View File

@@ -78,7 +78,7 @@
<input
id="login_input_master-password"
bitInput
type="{{ showPassword ? 'text' : 'password' }}"
[type]="showPassword ? 'text' : 'password'"
formControlName="masterPassword"
appAutofocus
/>

View File

@@ -34,7 +34,7 @@
<input
id="register-form_input_master-password"
bitInput
type="{{ showPassword ? 'text' : 'password' }}"
[type]="showPassword ? 'text' : 'password'"
formControlName="masterPassword"
/>
<button type="button" bitSuffix bitButton (click)="togglePassword()">
@@ -65,7 +65,7 @@
<input
id="register-form_input_confirm-master-password"
bitInput
type="{{ showPassword ? 'text' : 'password' }}"
[type]="showPassword ? 'text' : 'password'"
formControlName="confirmMasterPassword"
/>
<button type="button" bitSuffix bitButton (click)="togglePassword()">

View File

@@ -1,210 +0,0 @@
<div class="layout" [ngClass]="['layout', layout]">
<!-- TEAMS 1 Header -->
<header
class="header"
*ngIf="
layout === 'default' ||
layout === 'teams' ||
layout === 'teams1' ||
layout === 'teams2' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'enterprise2' ||
layout === 'cnetcmpgnent' ||
layout === 'cnetcmpgnteams' ||
layout === 'cnetcmpgnind'
"
>
<div class="container">
<div class="row">
<div class="col-7">
<img
alt="Bitwarden"
class="logo mb-2"
src="../../images/register-layout/logo-horizontal-white.svg"
/>
</div>
</div>
</div>
</header>
<div class="container">
<div class="row">
<div class="col-7" *ngIf="layout">
<div class="mt-5">
<!-- Default Body -->
<div
*ngIf="
layout === 'teams' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'default'
"
>
<h1>The Bitwarden Password Manager</h1>
<h2>
Trusted by millions of individuals, teams, and organizations worldwide for secure
password storage and sharing.
</h2>
<p>Store logins, secure notes, and more</p>
<p>Collaborate and share securely</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
</div>
<!-- Teams & Enterprise Body -->
<div *ngIf="layout === 'teams1' || layout === 'teams2' || layout === 'enterprise2'">
<h1>
Start Your <span *ngIf="layout === 'teams1' || layout === 'teams1'">Teams<br /></span
><span *ngIf="layout === 'enterprise2'">Enterprise</span> Free Trial Now
</h1>
<h2>
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
password storage and sharing.
</h2>
<p>Collaborate and share securely</p>
<p>Deploy and manage quickly and easily</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
</div>
<!-- CNET Campaign Teams & Enterprise Body -->
<div *ngIf="layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnent'">
<h1>
Start Your <span *ngIf="layout === 'cnetcmpgnteams'">Teams<br /></span
><span *ngIf="layout === 'cnetcmpgnent'">Enterprise</span> Free Trial Now
</h1>
<h2>
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
password storage and sharing.
</h2>
<p>Collaborate and share securely</p>
<p>Deploy and manage quickly and easily</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
</div>
<!-- CNET Campaign Premium Body -->
<div *ngIf="layout === 'cnetcmpgnind'">
<h1>Start Your Premium Account Now</h1>
<h2>
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
password storage and sharing.
</h2>
<p>Store logins, secure notes, and more</p>
<p>Secure your account with advanced two-step login</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
</div>
</div>
</div>
<div [ngClass]="{ 'col-5': layout, 'col-12': !layout }">
<div class="row justify-content-md-center mt-5">
<div [ngClass]="{ 'col-5': !layout, 'col-12': layout }">
<h1 class="lead text-center mb-4" *ngIf="!layout">{{ "createAccount" | i18n }}</h1>
<div class="card d-block">
<div class="card-body">
<app-callout
title="{{ 'createOrganizationStep1' | i18n }}"
type="info"
icon="bwi bwi-thumb-tack"
*ngIf="showCreateOrgMessage"
>
{{ "createOrganizationCreatePersonalAccount" | i18n }}
</app-callout>
<app-register-form
[queryParamEmail]="email"
[enforcedPolicyOptions]="enforcedPolicyOptions"
[referenceDataValue]="referenceData"
></app-register-form>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-7 d-flex align-items-center">
<div
*ngIf="
layout === 'cnetcmpgnent' || layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnind'
"
>
<figure>
<figcaption>
<cite>
<img
src="../../images/register-layout/cnet-logo.svg"
class="w-25 d-block mx-auto"
alt="cnet logo"
/>
</cite>
</figcaption>
<blockquote class="mx-auto text-center px-4">
"No more excuses; start using Bitwarden today. The identity you save could be your
own. The money definitely will be."
</blockquote>
</figure>
</div>
<div
*ngIf="
layout === 'teams' ||
layout === 'teams1' ||
layout === 'teams2' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'enterprise2' ||
layout === 'default'
"
>
<figure>
<figcaption>
<cite>
<img
src="../../images/register-layout/forbes-logo.svg"
class="w-25 d-block mx-auto"
alt="Forbes Logo"
/>
</cite>
</figcaption>
<blockquote class="mx-auto text-center px-4">
“Bitwarden boasts the backing of some of the world's best security experts and an
attractive, easy-to-use interface”
</blockquote>
</figure>
</div>
</div>
<div
*ngIf="
layout === 'cnetcmpgnent' || layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnind'
"
class="col-5 d-flex align-items-center justify-content-center"
>
<img
src="../../images/register-layout/usnews-360-badge.svg"
class="w-50 d-block"
alt="US News 360 Reviews Best Password Manager"
/>
</div>
<div
*ngIf="
layout === 'teams' ||
layout === 'teams1' ||
layout === 'teams2' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'enterprise2' ||
layout === 'default'
"
class="col-5 d-flex align-items-center justify-content-center"
>
<img
src="../../images/register-layout/usnews-360-badge.svg"
class="w-50 d-block"
alt="US News 360 Reviews Best Password Manager"
/>
</div>
</div>
</div>
</div>

View File

@@ -1,149 +0,0 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { UntypedFormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { first } from "rxjs/operators";
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { PolicyData } from "@bitwarden/common/models/data/policy.data";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/master-password-policy-options";
import { Policy } from "@bitwarden/common/models/domain/policy";
import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request";
import { RouterService } from "../core";
@Component({
selector: "app-register",
templateUrl: "register.component.html",
})
export class RegisterComponent extends BaseRegisterComponent implements OnInit, OnDestroy {
email = "";
showCreateOrgMessage = false;
layout = "";
enforcedPolicyOptions: MasterPasswordPolicyOptions;
private policies: Policy[];
private destroy$ = new Subject<void>();
constructor(
formValidationErrorService: FormValidationErrorsService,
formBuilder: UntypedFormBuilder,
authService: AuthService,
router: Router,
i18nService: I18nService,
cryptoService: CryptoService,
apiService: ApiService,
private route: ActivatedRoute,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
private policyApiService: PolicyApiServiceAbstraction,
private policyService: PolicyService,
environmentService: EnvironmentService,
logService: LogService,
private routerService: RouterService
) {
super(
formValidationErrorService,
formBuilder,
authService,
router,
i18nService,
cryptoService,
apiService,
stateService,
platformUtilsService,
passwordGenerationService,
environmentService,
logService
);
}
async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.route.queryParams.pipe(first()).subscribe((qParams) => {
this.referenceData = new ReferenceEventRequest();
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.routerService.setPreviousUrl("/settings/premium");
} else if (qParams.org != null) {
this.showCreateOrgMessage = true;
this.referenceData.flow = qParams.org;
const route = this.router.createUrlTree(["create-organization"], {
queryParams: { plan: qParams.org },
});
this.routerService.setPreviousUrl(route.toString());
}
if (qParams.layout != null) {
this.layout = this.referenceData.layout = qParams.layout;
}
if (qParams.reference != null) {
this.referenceData.id = qParams.reference;
} else {
this.referenceData.id = ("; " + document.cookie)
.split("; reference=")
.pop()
.split(";")
.shift();
}
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship
const route = this.router.createUrlTree(["setup/families-for-enterprise"], {
queryParams: { plan: qParams.sponsorshipToken },
});
this.routerService.setPreviousUrl(route.toString());
}
if (this.referenceData.id === "") {
this.referenceData.id = null;
}
});
const invite = await this.stateService.getOrganizationInvitation();
if (invite != null) {
try {
const policies = await this.policyApiService.getPoliciesByToken(
invite.organizationId,
invite.token,
invite.email,
invite.organizationUserId
);
if (policies.data != null) {
const policiesData = policies.data.map((p) => new PolicyData(p));
this.policies = policiesData.map((p) => new Policy(p));
}
} catch (e) {
this.logService.error(e);
}
}
if (this.policies != null) {
this.policyService
.masterPasswordPolicyOptions$(this.policies)
.pipe(takeUntil(this.destroy$))
.subscribe((enforcedPasswordPolicyOptions) => {
this.enforcedPolicyOptions = enforcedPasswordPolicyOptions;
});
}
await super.ngOnInit();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@@ -23,26 +23,39 @@
<div class="tw-pt-12">
<!-- Layout params are used by marketing to determine left-hand content -->
<app-default-content *ngIf="layout === 'default'"></app-default-content>
<app-teams-content *ngIf="layout === 'teams'"></app-teams-content>
<app-teams1-content *ngIf="layout === 'teams1'"></app-teams1-content>
<app-teams2-content *ngIf="layout === 'teams2'"></app-teams2-content>
<app-enterprise-content *ngIf="layout === 'enterprise'"></app-enterprise-content>
<app-enterprise1-content *ngIf="layout === 'enterprise1'"></app-enterprise1-content>
<app-enterprise2-content *ngIf="layout === 'enterprise2'"></app-enterprise2-content>
<app-default-content *ngIf="layout === layouts.default"></app-default-content>
<app-teams-content *ngIf="layout === layouts.teams"></app-teams-content>
<app-teams1-content *ngIf="layout === layouts.teams1"></app-teams1-content>
<app-teams2-content *ngIf="layout === layouts.teams2"></app-teams2-content>
<app-enterprise-content *ngIf="layout === layouts.enterprise"></app-enterprise-content>
<app-enterprise1-content *ngIf="layout === layouts.enterprise1"></app-enterprise1-content>
<app-enterprise2-content *ngIf="layout === layouts.enterprise2"></app-enterprise2-content>
<app-cnet-enterprise-content
*ngIf="layout === 'cnetcmpgnent'"
*ngIf="layout === layouts.cnetcmpgnent"
></app-cnet-enterprise-content>
<app-cnet-individual-content
*ngIf="layout === 'cnetcmpgnind'"
*ngIf="layout === layouts.cnetcmpgnind"
></app-cnet-individual-content>
<app-cnet-teams-content *ngIf="layout === 'cnetcmpgnteams'"></app-cnet-teams-content>
<app-abm-enterprise-content *ngIf="layout === 'abmenterprise'"></app-abm-enterprise-content>
<app-abm-teams-content *ngIf="layout === 'abmteams'"></app-abm-teams-content>
<app-cnet-teams-content *ngIf="layout === layouts.cnetcmpgnteams"></app-cnet-teams-content>
<app-abm-enterprise-content
*ngIf="layout === layouts.abmenterprise"
></app-abm-enterprise-content>
<app-abm-teams-content *ngIf="layout === layouts.abmteams"></app-abm-teams-content>
</div>
</div>
<div class="tw-w-1/2">
<div class="tw-pt-56">
<div *ngIf="!useTrialStepper">
<div
class="tw-min-w-xl tw-m-auto tw-mt-28 tw-max-w-xl tw-rounded tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-8"
>
<app-register-form
[queryParamEmail]="email"
[enforcedPolicyOptions]="enforcedPolicyOptions"
[referenceDataValue]="referenceData"
></app-register-form>
</div>
</div>
<div class="tw-pt-44" *ngIf="useTrialStepper">
<div class="tw-rounded tw-border tw-border-solid tw-border-secondary-300 tw-bg-background">
<div class="tw-flex tw-h-12 tw-w-full tw-items-center tw-rounded-t tw-bg-secondary-100">
<h2 class="tw-mb-0 tw-pl-4 tw-text-base tw-font-bold tw-uppercase">
@@ -62,7 +75,7 @@
<button
bitButton
buttonType="primary"
[disabled]="orgInfoFormGroup.get('name').hasError('required')"
[disabled]="orgInfoFormGroup.get('name').invalid"
cdkStepperNext
>
Next

View File

@@ -168,8 +168,9 @@ describe("TrialInitiationComponent", () => {
it("should set org variable to be enterprise and plan to EnterpriseAnnually if org param is enterprise", fakeAsync(() => {
mockQueryParams.next({ org: "enterprise" });
tick(); // wait for resolution
fixture = TestBed.createComponent(TrialInitiationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
component.ngOnInit();
expect(component.org).toBe("enterprise");
expect(component.plan).toBe(PlanType.EnterpriseAnnually);
}));
@@ -182,13 +183,33 @@ describe("TrialInitiationComponent", () => {
expect(component.org).toBe("");
expect(component.accountCreateOnly).toBe(true);
}));
it("should set the org to be families and plan to FamiliesAnnually if org param is invalid ", fakeAsync(async () => {
it("should not set the org if org param is invalid ", fakeAsync(async () => {
mockQueryParams.next({ org: "hahahaha" });
tick(); // wait for resolution
fixture = TestBed.createComponent(TrialInitiationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
expect(component.org).toBe("");
expect(component.accountCreateOnly).toBe(true);
}));
it("should set the layout variable if layout param is valid ", fakeAsync(async () => {
mockQueryParams.next({ layout: "teams1" });
tick(); // wait for resolution
fixture = TestBed.createComponent(TrialInitiationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
expect(component.layout).toBe("teams1");
expect(component.accountCreateOnly).toBe(false);
}));
it("should not set the layout variable and leave as 'default' if layout param is invalid ", fakeAsync(async () => {
mockQueryParams.next({ layout: "asdfasdf" });
tick(); // wait for resolution
fixture = TestBed.createComponent(TrialInitiationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
component.ngOnInit();
expect(component.org).toBe("families");
expect(component.plan).toBe(PlanType.FamiliesAnnually);
expect(component.layout).toBe("default");
expect(component.accountCreateOnly).toBe(true);
}));
});

View File

@@ -20,6 +20,30 @@ import { ReferenceEventRequest } from "@bitwarden/common/models/request/referenc
import { RouterService } from "./../../core/router.service";
import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component";
enum ValidOrgParams {
families = "families",
enterprise = "enterprise",
teams = "teams",
individual = "individual",
premium = "premium",
free = "free",
}
enum ValidLayoutParams {
default = "default",
teams = "teams",
teams1 = "teams1",
teams2 = "teams2",
enterprise = "enterprise",
enterprise1 = "enterprise1",
enterprise2 = "enterprise2",
cnetcmpgnent = "cnetcmpgnent",
cnetcmpgnind = "cnetcmpgnind",
cnetcmpgnteams = "cnetcmpgnteams",
abmenterprise = "abmenterprise",
abmteams = "abmteams",
}
@Component({
selector: "app-trial",
templateUrl: "trial-initiation.component.html",
@@ -35,14 +59,25 @@ export class TrialInitiationComponent implements OnInit, OnDestroy {
plan: PlanType;
product: ProductType;
accountCreateOnly = true;
useTrialStepper = false;
policies: Policy[];
enforcedPolicyOptions: MasterPasswordPolicyOptions;
validOrgs: string[] = ["teams", "enterprise", "families"];
trialFlowOrgs: string[] = [
ValidOrgParams.teams,
ValidOrgParams.enterprise,
ValidOrgParams.families,
];
routeFlowOrgs: string[] = [
ValidOrgParams.free,
ValidOrgParams.premium,
ValidOrgParams.individual,
];
layouts = ValidLayoutParams;
referenceData: ReferenceEventRequest;
@ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent;
orgInfoFormGroup = this.formBuilder.group({
name: ["", [Validators.required]],
name: ["", { validators: [Validators.required, Validators.maxLength(50)], updateOn: "change" }],
email: [""],
});
@@ -87,39 +122,38 @@ export class TrialInitiationComponent implements OnInit, OnDestroy {
this.referenceDataId = qParams.reference;
if (!qParams.org) {
return;
}
if (qParams.layout) {
if (Object.values(ValidLayoutParams).includes(qParams.layout)) {
this.layout = qParams.layout;
this.accountCreateOnly = false;
}
if (this.validOrgs.includes(qParams.org)) {
if (this.trialFlowOrgs.includes(qParams.org)) {
this.org = qParams.org;
} else {
this.org = "families";
}
this.orgLabel = this.titleCasePipe.transform(this.org);
this.useTrialStepper = true;
this.referenceData.flow = qParams.org;
this.referenceData.flow = qParams.org;
if (this.org === ValidOrgParams.families) {
this.plan = PlanType.FamiliesAnnually;
this.product = ProductType.Families;
} else if (this.org === ValidOrgParams.teams) {
this.plan = PlanType.TeamsAnnually;
this.product = ProductType.Teams;
} else if (this.org === ValidOrgParams.enterprise) {
this.plan = PlanType.EnterpriseAnnually;
this.product = ProductType.Enterprise;
}
} else if (this.routeFlowOrgs.includes(qParams.org)) {
this.referenceData.flow = qParams.org;
const route = this.router.createUrlTree(["create-organization"], {
queryParams: { plan: qParams.org },
});
this.routerService.setPreviousUrl(route.toString());
}
// Are they coming from an email for sponsoring a families organization
// After logging in redirect them to setup the families sponsorship
this.setupFamilySponsorship(qParams.sponsorshipToken);
this.orgLabel = this.titleCasePipe.transform(this.org);
this.accountCreateOnly = false;
if (this.org === "families") {
this.plan = PlanType.FamiliesAnnually;
this.product = ProductType.Families;
} else if (this.org === "teams") {
this.plan = PlanType.TeamsAnnually;
this.product = ProductType.Teams;
} else if (this.org === "enterprise") {
this.plan = PlanType.EnterpriseAnnually;
this.product = ProductType.Enterprise;
}
});
const invite = await this.stateService.getOrganizationInvitation();
@@ -148,6 +182,12 @@ export class TrialInitiationComponent implements OnInit, OnDestroy {
this.enforcedPolicyOptions = enforcedPasswordPolicyOptions;
});
}
this.orgInfoFormGroup.controls.name.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.orgInfoFormGroup.controls.name.markAsTouched();
});
}
ngOnDestroy(): void {

View File

@@ -1,5 +1,7 @@
import { CdkStepper } from "@angular/cdk/stepper";
import { Component, Input } from "@angular/core";
import { Component, Input, QueryList } from "@angular/core";
import { VerticalStep } from "./vertical-step.component";
@Component({
selector: "app-vertical-stepper",
@@ -7,6 +9,8 @@ import { Component, Input } from "@angular/core";
providers: [{ provide: CdkStepper, useExisting: VerticalStepperComponent }],
})
export class VerticalStepperComponent extends CdkStepper {
readonly steps: QueryList<VerticalStep>;
@Input()
activeClass = "active";

View File

@@ -80,7 +80,7 @@
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame" class="mb-3">
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
<iframe id="webauthn_iframe" [attr.allow]="webAuthnAllow"></iframe>
</div>
</ng-container>
<ng-container

View File

@@ -12,7 +12,7 @@ import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.s
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
import { InternalFolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
@@ -74,7 +74,7 @@ export class AppComponent implements OnDestroy, OnInit {
private notificationsService: NotificationsService,
private routerService: RouterService,
private stateService: StateService,
private eventService: EventService,
private eventUploadService: EventUploadService,
private policyService: InternalPolicyService,
protected policyListService: PolicyListService,
private keyConnectorService: KeyConnectorService
@@ -221,10 +221,9 @@ export class AppComponent implements OnDestroy, OnInit {
}
private async logOut(expired: boolean) {
await this.eventService.uploadEvents();
await this.eventUploadService.uploadEvents();
const userId = await this.stateService.getUserId();
await Promise.all([
this.eventService.clearEvents(),
this.syncService.setLastSync(new Date(0)),
this.cryptoService.clearKeys(),
this.settingsService.clear(userId),

View File

@@ -4,6 +4,7 @@ import { map, Observable } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import {
canAccessAdmin,
isNotProviderUser,
OrganizationService,
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { Utils } from "@bitwarden/common/misc/utils";
@@ -23,6 +24,7 @@ export class OrganizationSwitcherComponent implements OnInit {
async ngOnInit() {
this.organizations$ = this.organizationService.organizations$.pipe(
map((orgs) => orgs.filter(isNotProviderUser)),
canAccessAdmin(this.i18nService),
map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name")))
);

View File

@@ -0,0 +1,46 @@
<ng-container *ngIf="!usesKeyConnector">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
</ng-container>
<ng-container *ngIf="usesKeyConnector">
<div class="form-group">
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
<button
type="button"
class="btn btn-outline-secondary"
(click)="requestOTP()"
[disabled]="disableRequestOTP"
>
{{ "sendCode" | i18n }}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
{{ "codeSent" | i18n }}
</span>
</div>
<div class="form-group">
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
<input
id="verificationCode"
type="input"
name="verificationCode"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
</div>
</ng-container>

View File

@@ -0,0 +1,23 @@
import { animate, style, transition, trigger } from "@angular/animations";
import { Component } from "@angular/core";
import { NG_VALUE_ACCESSOR } from "@angular/forms";
import { UserVerificationComponent as BaseComponent } from "@bitwarden/angular/components/user-verification.component";
@Component({
selector: "app-user-verification",
templateUrl: "user-verification.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: UserVerificationComponent,
},
],
animations: [
trigger("sent", [
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
]),
],
})
export class UserVerificationComponent extends BaseComponent {}

View File

@@ -8,14 +8,14 @@ import {
EnvironmentService as EnvironmentServiceAbstraction,
Urls,
} from "@bitwarden/common/abstractions/environment.service";
import { EventService as EventLoggingServiceAbstraction } from "@bitwarden/common/abstractions/event.service";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service";
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/abstractions/twoFactor.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { ContainerService } from "@bitwarden/common/services/container.service";
import { EventService as EventLoggingService } from "@bitwarden/common/services/event.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { VaultTimeoutService as VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vaultTimeout.service";
import { I18nService } from "./i18n.service";
@@ -28,7 +28,7 @@ export class InitService {
private notificationsService: NotificationsServiceAbstraction,
private vaultTimeoutService: VaultTimeoutServiceAbstraction,
private i18nService: I18nServiceAbstraction,
private eventLoggingService: EventLoggingServiceAbstraction,
private eventUploadService: EventUploadServiceAbstraction,
private twoFactorService: TwoFactorServiceAbstraction,
private stateService: StateServiceAbstraction,
private cryptoService: CryptoServiceAbstraction,
@@ -48,7 +48,7 @@ export class InitService {
(this.vaultTimeoutService as VaultTimeoutService).init(true);
const locale = await this.stateService.getLocale();
await (this.i18nService as I18nService).init(locale);
(this.eventLoggingService as EventLoggingService).init(true);
(this.eventUploadService as EventUploadService).init(true);
this.twoFactorService.init();
const htmlEl = this.win.document.documentElement;
htmlEl.classList.add("locale_" + this.i18nService.translationLocale);

View File

@@ -0,0 +1,115 @@
import { WebPlatformUtilsService } from "./web-platform-utils.service";
describe("Web Platform Utils Service", () => {
let webPlatformUtilsService: WebPlatformUtilsService;
beforeEach(() => {
webPlatformUtilsService = new WebPlatformUtilsService(null, null, null);
});
afterEach(() => {
delete process.env.APPLICATION_VERSION;
});
describe("getApplicationVersion", () => {
test("null", async () => {
delete process.env.APPLICATION_VERSION;
const result = await webPlatformUtilsService.getApplicationVersion();
expect(result).toBe("-");
});
test("<empty>", async () => {
process.env.APPLICATION_VERSION = "";
const result = await webPlatformUtilsService.getApplicationVersion();
expect(result).toBe("-");
});
test("{version number}", async () => {
process.env.APPLICATION_VERSION = "2022.10.2";
const result = await webPlatformUtilsService.getApplicationVersion();
expect(result).toBe("2022.10.2");
});
test("{version number} - {git hash}", async () => {
process.env.APPLICATION_VERSION = "2022.10.2 - 5f8c1c1";
const result = await webPlatformUtilsService.getApplicationVersion();
expect(result).toBe("2022.10.2 - 5f8c1c1");
});
test("{version number}-{git hash}", async () => {
process.env.APPLICATION_VERSION = "2022.10.2-5f8c1c1";
const result = await webPlatformUtilsService.getApplicationVersion();
expect(result).toBe("2022.10.2-5f8c1c1");
});
test("{version number} + {git hash}", async () => {
process.env.APPLICATION_VERSION = "2022.10.2 + 5f8c1c1";
const result = await webPlatformUtilsService.getApplicationVersion();
expect(result).toBe("2022.10.2 + 5f8c1c1");
});
test("{version number}+{git hash}", async () => {
process.env.APPLICATION_VERSION = "2022.10.2+5f8c1c1";
const result = await webPlatformUtilsService.getApplicationVersion();
expect(result).toBe("2022.10.2+5f8c1c1");
});
});
describe("getApplicationVersionNumber", () => {
test("null", async () => {
delete process.env.APPLICATION_VERSION;
const result = await webPlatformUtilsService.getApplicationVersionNumber();
expect(result).toBe("");
});
test("<empty>", async () => {
process.env.APPLICATION_VERSION = "";
const result = await webPlatformUtilsService.getApplicationVersionNumber();
expect(result).toBe("");
});
test("{version number}", async () => {
process.env.APPLICATION_VERSION = "2022.10.2";
const result = await webPlatformUtilsService.getApplicationVersionNumber();
expect(result).toBe("2022.10.2");
});
test("{version number} - {git hash}", async () => {
process.env.APPLICATION_VERSION = "2022.10.2 - 5f8c1c1";
const result = await webPlatformUtilsService.getApplicationVersionNumber();
expect(result).toBe("2022.10.2");
});
test("{version number}-{git hash}", async () => {
process.env.APPLICATION_VERSION = "2022.10.2-5f8c1c1";
const result = await webPlatformUtilsService.getApplicationVersionNumber();
expect(result).toBe("2022.10.2");
});
test("{version number} + {git hash}", async () => {
process.env.APPLICATION_VERSION = "2022.10.2 + 5f8c1c1";
const result = await webPlatformUtilsService.getApplicationVersionNumber();
expect(result).toBe("2022.10.2");
});
test("{version number}+{git hash}", async () => {
process.env.APPLICATION_VERSION = "2022.10.2+5f8c1c1";
const result = await webPlatformUtilsService.getApplicationVersionNumber();
expect(result).toBe("2022.10.2");
});
});
});

View File

@@ -108,6 +108,10 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
return Promise.resolve(process.env.APPLICATION_VERSION || "-");
}
async getApplicationVersionNumber(): Promise<string> {
return (await this.getApplicationVersion()).split(RegExp("[+|-]"))[0].trim();
}
supportsWebAuthn(win: Window): boolean {
return typeof PublicKeyCredential !== "undefined";
}

View File

@@ -1,10 +1,11 @@
import { Component, OnInit } from "@angular/core";
import { Observable } from "rxjs";
import { map, Observable } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import {
canAccessAdmin,
isNotProviderUser,
OrganizationService,
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@@ -54,6 +55,7 @@ export class NavbarComponent implements OnInit {
this.providers = await this.providerService.getAll();
this.organizations$ = this.organizationService.organizations$.pipe(
map((orgs) => orgs.filter(isNotProviderUser)),
canAccessAdmin(this.i18nService)
);
}

View File

@@ -44,7 +44,7 @@
class="btn btn-outline-secondary btn-submit"
(click)="reinstate()"
[appApiAction]="reinstatePromise"
[disabled]="reinstateBtn.loading"
[disabled]="$any(reinstateBtn).loading"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "reinstateSubscription" | i18n }}</span>
@@ -113,8 +113,8 @@
</button>
<app-change-plan
[organizationId]="organizationId"
(onChanged)="closeChangePlan(true)"
(onCanceled)="closeChangePlan(false)"
(onChanged)="closeChangePlan()"
(onCanceled)="closeChangePlan()"
*ngIf="showChangePlan"
></app-change-plan>
</ng-container>
@@ -143,7 +143,7 @@
class="btn btn-outline-danger btn-submit"
(click)="removeSponsorship()"
[appApiAction]="removeSponsorshipPromise"
[disabled]="removeSponsorshipBtn.loading"
[disabled]="$any(removeSponsorshipBtn).loading"
*ngIf="isSponsoredSubscription"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
@@ -230,7 +230,7 @@
class="btn btn-outline-danger btn-submit ml-1"
(click)="cancel()"
[appApiAction]="cancelPromise"
[disabled]="cancelBtn.loading"
[disabled]="$any(cancelBtn).loading"
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>

View File

@@ -52,11 +52,11 @@
type="button"
class="btn btn-sm btn-outline-primary ml-3"
(click)="loadEvents(true)"
[disabled]="loaded && refreshBtn.loading"
[disabled]="loaded && $any(refreshBtn).loading"
>
<i
class="bwi bwi-refresh bwi-fw"
[ngClass]="{ 'bwi-spin': loaded && refreshBtn.loading }"
[ngClass]="{ 'bwi-spin': loaded && $any(refreshBtn).loading }"
aria-hidden="true"
></i>
{{ "refresh" | i18n }}
@@ -101,7 +101,7 @@
type="button"
class="btn btn-block btn-link btn-submit"
(click)="loadEvents(false)"
[disabled]="loaded && moreBtn.loading"
[disabled]="loaded && $any(moreBtn).loading"
*ngIf="continuationToken"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>

View File

@@ -95,7 +95,7 @@
bitButton
buttonType="primary"
(click)="loadEvents(false)"
[disabled]="loaded && moreBtn.loading"
[disabled]="loaded && $any(moreBtn).loading"
*ngIf="continuationToken"
>
<i

View File

@@ -113,12 +113,23 @@
name="userType"
id="userTypeCustom"
[value]="organizationUserType.Custom"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="type"
[attr.disabled]="!canUseCustomPermissions || null"
/>
<label class="form-check-label" for="userTypeCustom">
{{ "custom" | i18n }}
<small>{{ "customDesc" | i18n }}</small>
<ng-container *ngIf="!canUseCustomPermissions; else enterprise">
<small
>{{ "customDescNonEnterpriseStart" | i18n
}}<a href="https://bitwarden.com/contact/" target="_blank">{{
"customDescNonEnterpriseLink" | i18n
}}</a
>{{ "customDescNonEnterpriseEnd" | i18n }}</small
>
</ng-container>
<ng-template #enterprise>
<small>{{ "customDesc" | i18n }}</small>
</ng-template>
</label>
</div>
<ng-container *ngIf="customUserTypeSelected">
@@ -212,7 +223,7 @@
class="form-check-input"
type="checkbox"
name="manageSso"
id="managePolicies"
id="manageSso"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="permissions.manageSso"
/>
@@ -345,7 +356,7 @@
<input
type="checkbox"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="c.checked"
[(ngModel)]="$any(c).checked"
name="Collection[{{ i }}].Checked"
appStopProp
/>
@@ -359,7 +370,7 @@
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="c.hidePasswords"
name="Collection[{{ i }}].HidePasswords"
[disabled]="!c.checked"
[disabled]="!$any(c).checked"
/>
</td>
<td class="text-center">
@@ -368,7 +379,7 @@
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="c.readOnly"
name="Collection[{{ i }}].ReadOnly"
[disabled]="!c.checked"
[disabled]="!$any(c).checked"
/>
</td>
</tr>

View File

@@ -6,6 +6,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
@@ -57,6 +58,7 @@ export class MemberDialogComponent implements OnInit {
access: "all" | "selected" = "selected";
collections: CollectionView[] = [];
organizationUserType = OrganizationUserType;
canUseCustomPermissions: boolean;
protected tabIndex: MemberDialogTab;
// Stub, to be filled out in upcoming PRs
@@ -104,6 +106,7 @@ export class MemberDialogComponent implements OnInit {
private i18nService: I18nService,
private collectionService: CollectionService,
private platformUtilsService: PlatformUtilsService,
private organizationService: OrganizationService,
private logService: LogService,
private formBuilder: FormBuilder
) {}
@@ -111,6 +114,9 @@ export class MemberDialogComponent implements OnInit {
async ngOnInit() {
this.editMode = this.loading = this.params.organizationUserId != null;
this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;
const organization = this.organizationService.get(this.params.organizationId);
this.canUseCustomPermissions = organization.useCustomPermissions;
await this.loadCollections();
if (this.editMode) {
@@ -185,6 +191,14 @@ export class MemberDialogComponent implements OnInit {
}
submit = async () => {
if (!this.canUseCustomPermissions && this.type === OrganizationUserType.Custom) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("customNonEnterpriseError")
);
return;
}
let collections: SelectionReadOnlyRequest[] = null;
if (this.access !== "all") {
collections = this.collections
@@ -194,30 +208,9 @@ export class MemberDialogComponent implements OnInit {
try {
if (this.editMode) {
const request = new OrganizationUserUpdateRequest();
request.accessAll = this.access === "all";
request.type = this.type;
request.collections = collections;
request.permissions = this.setRequestPermissions(
request.permissions ?? new PermissionsApi(),
request.type !== OrganizationUserType.Custom
);
await this.apiService.putOrganizationUser(
this.params.organizationId,
this.params.organizationUserId,
request
);
await this.updateUser(collections);
} else {
const request = new OrganizationUserInviteRequest();
request.emails = [...new Set(this.emails.trim().split(/\s*,\s*/))];
request.accessAll = this.access === "all";
request.type = this.type;
request.permissions = this.setRequestPermissions(
request.permissions ?? new PermissionsApi(),
request.type !== OrganizationUserType.Custom
);
request.collections = collections;
await this.apiService.postOrganizationUserInvite(this.params.organizationId, request);
await this.inviteUser(collections);
}
this.platformUtilsService.showToast(
@@ -331,6 +324,35 @@ export class MemberDialogComponent implements OnInit {
private close(result: MemberDialogResult) {
this.dialogRef.close(result);
}
async updateUser(collections: SelectionReadOnlyRequest[]) {
const request = new OrganizationUserUpdateRequest();
request.accessAll = this.access === "all";
request.type = this.type;
request.collections = collections;
request.permissions = this.setRequestPermissions(
request.permissions ?? new PermissionsApi(),
request.type !== OrganizationUserType.Custom
);
await this.apiService.putOrganizationUser(
this.params.organizationId,
this.params.organizationUserId,
request
);
}
async inviteUser(collections: SelectionReadOnlyRequest[]) {
const request = new OrganizationUserInviteRequest();
request.emails = [...new Set(this.emails.trim().split(/\s*,\s*/))];
request.accessAll = this.access === "all";
request.type = this.type;
request.permissions = this.setRequestPermissions(
request.permissions ?? new PermissionsApi(),
request.type !== OrganizationUserType.Custom
);
request.collections = collections;
await this.apiService.postOrganizationUserInvite(this.params.organizationId, request);
}
}
/**

View File

@@ -34,7 +34,7 @@
<td class="table-list-checkbox" (click)="check(g)">
<input
type="checkbox"
[(ngModel)]="g.checked"
[(ngModel)]="$any(g).checked"
name="Groups[{{ i }}].Checked"
appStopProp
/>

View File

@@ -2,8 +2,6 @@ import { NgModule } from "@angular/core";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { SharedModule } from "../shared";
import { CoreOrganizationModule } from "./core";
import { GroupAddEditComponent } from "./manage/group-add-edit.component";
import { GroupsComponent } from "./manage/groups.component";
@@ -14,12 +12,7 @@ import { GroupService } from "./services/group/group.service";
import { SharedOrganizationModule } from "./shared";
@NgModule({
imports: [
SharedModule,
OrganizationsRoutingModule,
SharedOrganizationModule,
CoreOrganizationModule,
],
imports: [OrganizationsRoutingModule, SharedOrganizationModule, CoreOrganizationModule],
declarations: [GroupsComponent, GroupAddEditComponent, UserGroupsComponent],
providers: [
{

View File

@@ -29,7 +29,7 @@ export class PoliciesComponent implements OnInit {
organization: Organization;
private orgPolicies: PolicyResponse[];
private policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
protected policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
constructor(
private route: ActivatedRoute,

View File

@@ -53,11 +53,13 @@
<div class="tw-flex tw-flex-col">
<div class="tw-text-sm">
{{ item.labelName }}
<span *ngIf="item.status == 0" bitBadge badgeType="secondary">
<span *ngIf="$any(item).status == 0" bitBadge badgeType="secondary">
{{ "invited" | i18n }}
</span>
</div>
<div class="tw-text-xs tw-text-muted" *ngIf="item.status != 0">{{ item.email }}</div>
<div class="tw-text-xs tw-text-muted" *ngIf="$any(item).status != 0">
{{ $any(item).email }}
</div>
</div>
</div>
@@ -110,11 +112,11 @@
</td>
<td bitCell *ngIf="showMemberRoles">
{{ item.role | userType: "-" }}
{{ $any(item).role | userType: "-" }}
</td>
<td bitCell *ngIf="showGroupColumn">
{{ item.viaGroupName ?? "-" }}
{{ $any(item).viaGroupName ?? "-" }}
</td>
<td bitCell>

View File

@@ -44,7 +44,7 @@
<bit-tab label="{{ 'access' | i18n }}">
<bit-access-selector
*ngIf="organization.useGroups"
permissionMode="edit"
[permissionMode]="PermissionMode.Edit"
formControlName="access"
[items]="accessItems"
[columnHeader]="'groupAndMemberColumnHeader' | i18n"
@@ -54,7 +54,7 @@
></bit-access-selector>
<bit-access-selector
*ngIf="!organization.useGroups"
permissionMode="edit"
[permissionMode]="PermissionMode.Edit"
formControlName="access"
[items]="accessItems"
[columnHeader]="'memberColumnHeader' | i18n"

View File

@@ -21,6 +21,7 @@ import {
AccessItemView,
convertToPermission,
convertToSelectionView,
PermissionMode,
} from "../access-selector";
export interface CollectionDialogParams {
@@ -53,6 +54,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
parent: null as string | null,
access: [[] as AccessItemValue[]],
});
protected PermissionMode = PermissionMode;
constructor(
@Inject(DIALOG_DATA) private params: CollectionDialogParams,

View File

@@ -0,0 +1,21 @@
<label class="tw-sr-only" [for]="id">{{ "search" | i18n }}</label>
<div class="tw-relative tw-flex tw-items-center">
<label
[for]="id"
aria-hidden="true"
class="tw-absolute tw-left-2 tw-z-20 !tw-mb-0 tw-cursor-text"
>
<i class="bwi bwi-search bwi-fw tw-text-muted"></i>
</label>
<input
bitInput
type="search"
[id]="id"
[placeholder]="placeholder ?? ('search' | i18n)"
class="tw-rounded-l tw-pl-9"
[ngModel]="searchText"
(ngModelChange)="onChange($event)"
(blur)="onTouch()"
[disabled]="disabled"
/>
</div>

View File

@@ -0,0 +1,54 @@
import { Component, Input } from "@angular/core";
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
let nextId = 0;
@Component({
selector: "app-search-input",
templateUrl: "./search-input.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: SearchInputComponent,
},
],
})
export class SearchInputComponent implements ControlValueAccessor {
private notifyOnChange: (v: string) => void;
private notifyOnTouch: () => void;
protected id = `search-id-${nextId++}`;
protected searchText: string;
@Input() disabled: boolean;
@Input() placeholder: string;
onChange(searchText: string) {
if (this.notifyOnChange != undefined) {
this.notifyOnChange(searchText);
}
}
onTouch() {
if (this.notifyOnTouch != undefined) {
this.notifyOnTouch();
}
}
registerOnChange(fn: (v: string) => void): void {
this.notifyOnChange = fn;
}
registerOnTouched(fn: () => void): void {
this.notifyOnTouch = fn;
}
writeValue(searchText: string): void {
this.searchText = searchText;
}
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
}

View File

@@ -0,0 +1,36 @@
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { InputModule } from "@bitwarden/components/src/input/input.module";
import { PreloadedEnglishI18nModule } from "../../../../tests/preloaded-english-i18n.module";
import { SearchInputComponent } from "./search-input.component";
export default {
title: "Web/Organizations/Search Input",
component: SearchInputComponent,
decorators: [
moduleMetadata({
imports: [
InputModule,
FormsModule,
ReactiveFormsModule,
PreloadedEnglishI18nModule,
JslibModule,
],
providers: [],
}),
],
} as Meta;
const Template: Story<SearchInputComponent> = (args: SearchInputComponent) => ({
props: args,
template: `
<app-search-input [(ngModel)]="searchText" [placeholder]="placeholder" [disabled]="disabled"></app-search-input>
`,
});
export const Default = Template.bind({});
Default.args = {};

View File

@@ -1,10 +1,14 @@
import { NgModule } from "@angular/core";
import { SharedModule } from "../../shared/shared.module";
import { AccessSelectorModule } from "./components/access-selector";
import { CollectionDialogModule } from "./components/collection-dialog";
import { SearchInputComponent } from "./components/search-input/search-input.component";
@NgModule({
imports: [CollectionDialogModule, AccessSelectorModule],
exports: [CollectionDialogModule, AccessSelectorModule],
imports: [SharedModule, CollectionDialogModule, AccessSelectorModule],
declarations: [SearchInputComponent],
exports: [SharedModule, CollectionDialogModule, AccessSelectorModule],
})
export class SharedOrganizationModule {}

View File

@@ -4,7 +4,7 @@ import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { ExportService } from "@bitwarden/common/abstractions/export.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -27,7 +27,7 @@ export class OrganizationExportComponent extends ExportComponent {
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
exportService: ExportService,
eventService: EventService,
eventCollectionService: EventCollectionService,
private route: ActivatedRoute,
policyService: PolicyService,
logService: LogService,
@@ -41,7 +41,7 @@ export class OrganizationExportComponent extends ExportComponent {
i18nService,
platformUtilsService,
exportService,
eventService,
eventCollectionService,
policyService,
logService,
userVerificationService,
@@ -76,7 +76,7 @@ export class OrganizationExportComponent extends ExportComponent {
}
async collectEvent(): Promise<void> {
await this.eventService.collect(
await this.eventCollectionService.collect(
EventType.Organization_ClientExportedVault,
null,
null,

View File

@@ -4,7 +4,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
@@ -18,7 +18,6 @@ import { StateService } from "@bitwarden/common/abstractions/state.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { CipherData } from "@bitwarden/common/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/models/domain/cipher";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherCreateRequest } from "@bitwarden/common/models/request/cipher-create.request";
import { CipherRequest } from "@bitwarden/common/models/request/cipher.request";
@@ -29,7 +28,6 @@ import { AddEditComponent as BaseAddEditComponent } from "../../vault/add-edit.c
templateUrl: "../../vault/add-edit.component.html",
})
export class AddEditComponent extends BaseAddEditComponent {
organization: Organization;
originalCipher: Cipher = null;
constructor(
@@ -44,7 +42,7 @@ export class AddEditComponent extends BaseAddEditComponent {
passwordGenerationService: PasswordGenerationService,
private apiService: ApiService,
messagingService: MessagingService,
eventService: EventService,
eventCollectionService: EventCollectionService,
policyService: PolicyService,
logService: LogService,
passwordRepromptService: PasswordRepromptService,
@@ -61,7 +59,7 @@ export class AddEditComponent extends BaseAddEditComponent {
totpService,
passwordGenerationService,
messagingService,
eventService,
eventCollectionService,
policyService,
organizationService,
logService,

View File

@@ -2,7 +2,7 @@ import { Component, EventEmitter, Output } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
@@ -12,21 +12,17 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { TokenService } from "@bitwarden/common/abstractions/token.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CiphersComponent as BaseCiphersComponent } from "../../vault/ciphers.component";
import { VaultItemsComponent as BaseVaultItemsComponent } from "../../vault/vault-items.component";
@Component({
selector: "app-org-vault-ciphers",
templateUrl: "../../vault/ciphers.component.html",
selector: "app-org-vault-items",
templateUrl: "../../vault/vault-items.component.html",
})
export class CiphersComponent extends BaseCiphersComponent {
export class VaultItemsComponent extends BaseVaultItemsComponent {
@Output() onEventsClicked = new EventEmitter<CipherView>();
organization: Organization;
accessEvents = false;
protected allCiphers: CipherView[] = [];
constructor(
@@ -34,7 +30,7 @@ export class CiphersComponent extends BaseCiphersComponent {
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
cipherService: CipherService,
eventService: EventService,
eventCollectionService: EventCollectionService,
totpService: TotpService,
passwordRepromptService: PasswordRepromptService,
logService: LogService,
@@ -48,7 +44,7 @@ export class CiphersComponent extends BaseCiphersComponent {
i18nService,
platformUtilsService,
cipherService,
eventService,
eventCollectionService,
totpService,
stateService,
passwordRepromptService,
@@ -86,6 +82,7 @@ export class CiphersComponent extends BaseCiphersComponent {
async search(timeout: number = null) {
await super.search(timeout, this.allCiphers);
}
events(c: CipherView) {
this.onEventsClicked.emit(c);
}

View File

@@ -19,8 +19,8 @@
<div class="page-header d-flex">
<h1>
{{ "vaultItems" | i18n }}
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<small #actionSpinner [appApiAction]="vaultItemsComponent.actionPromise">
<ng-container *ngIf="$any(actionSpinner).loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
@@ -32,7 +32,7 @@
</h1>
<div class="ml-auto d-flex">
<app-vault-bulk-actions
[ciphersComponent]="ciphersComponent"
[vaultItemsComponent]="vaultItemsComponent"
[deleted]="activeFilter.isDeleted"
[organization]="organization"
>
@@ -54,7 +54,7 @@
>
{{ trashCleanupWarning }}
</app-callout>
<app-org-vault-ciphers
<app-org-vault-items
(onCipherClicked)="editCipher($event)"
(onAttachmentsClicked)="editCipherAttachments($event)"
(onAddCipher)="addCipher()"
@@ -62,7 +62,7 @@
(onEventsClicked)="viewEvents($event)"
(onCloneClicked)="cloneCipher($event)"
>
</app-org-vault-ciphers>
</app-org-vault-items>
</div>
</div>
</div>

View File

@@ -29,9 +29,9 @@ 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 { VaultFilterComponent } from "./vault-filter/vault-filter.component";
import { VaultItemsComponent } from "./vault-items.component";
const BroadcasterSubscriptionId = "OrgVaultComponent";
@@ -42,7 +42,7 @@ const BroadcasterSubscriptionId = "OrgVaultComponent";
export class VaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultFilter", { static: true })
vaultFilterComponent: VaultFilterComponent;
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
@ViewChild(VaultItemsComponent, { static: true }) vaultItemsComponent: VaultItemsComponent;
@ViewChild("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true })
@@ -82,11 +82,11 @@ export class VaultComponent implements OnInit, OnDestroy {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.params.subscribe(async (params: any) => {
this.organization = this.organizationService.get(params.organizationId);
this.ciphersComponent.organization = this.organization;
this.vaultItemsComponent.organization = this.organization;
/* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.ciphersComponent.searchText = this.vaultFilterComponent.searchText = qParams.search;
this.vaultItemsComponent.searchText = this.vaultFilterComponent.searchText = qParams.search;
if (!this.organization.canViewAllCollections) {
await this.syncService.fullSync(false);
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
@@ -96,7 +96,7 @@ export class VaultComponent implements OnInit, OnDestroy {
if (message.successfully) {
await Promise.all([
this.vaultFilterService.reloadCollections(),
this.ciphersComponent.refresh(),
this.vaultItemsComponent.refresh(),
]);
this.changeDetectorRef.detectChanges();
}
@@ -106,13 +106,15 @@ export class VaultComponent implements OnInit, OnDestroy {
});
}
await this.ciphersComponent.reload(
await this.vaultItemsComponent.reload(
this.activeFilter.buildFilter(),
this.activeFilter.isDeleted
);
if (qParams.viewEvents != null) {
const cipher = this.ciphersComponent.ciphers.filter((c) => c.id === qParams.viewEvents);
const cipher = this.vaultItemsComponent.ciphers.filter(
(c) => c.id === qParams.viewEvents
);
if (cipher.length > 0) {
this.viewEvents(cipher[0]);
}
@@ -151,8 +153,8 @@ export class VaultComponent implements OnInit, OnDestroy {
async applyVaultFilter(filter: VaultFilter) {
this.activeFilter = filter;
this.ciphersComponent.showAddNew = !this.activeFilter.isDeleted;
await this.ciphersComponent.reload(
this.vaultItemsComponent.showAddNew = !this.activeFilter.isDeleted;
await this.vaultItemsComponent.reload(
this.activeFilter.buildFilter(),
this.activeFilter.isDeleted
);
@@ -160,8 +162,8 @@ export class VaultComponent implements OnInit, OnDestroy {
}
filterSearchText(searchText: string) {
this.ciphersComponent.searchText = searchText;
this.ciphersComponent.search(200);
this.vaultItemsComponent.searchText = searchText;
this.vaultItemsComponent.search(200);
}
async editCipherAttachments(cipher: CipherView) {
@@ -188,7 +190,7 @@ export class VaultComponent implements OnInit, OnDestroy {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
modal.onClosed.subscribe(async () => {
if (madeAttachmentChanges) {
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
}
madeAttachmentChanges = false;
});
@@ -207,7 +209,7 @@ export class VaultComponent implements OnInit, OnDestroy {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onSavedCollections.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
}
);
@@ -247,17 +249,17 @@ export class VaultComponent implements OnInit, OnDestroy {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onSavedCipher.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onDeletedCipher.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onRestoredCipher.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
}
);

View File

@@ -2,15 +2,22 @@ import { NgModule } from "@angular/core";
import { LooseComponentsModule } from "../../shared/loose-components.module";
import { SharedModule } from "../../shared/shared.module";
import { OrganizationBadgeModule } from "../../vault/organization-badge/organization-badge.module";
import { CiphersComponent } from "./ciphers.component";
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
import { VaultItemsComponent } from "./vault-items.component";
import { VaultRoutingModule } from "./vault-routing.module";
import { VaultComponent } from "./vault.component";
@NgModule({
imports: [VaultRoutingModule, VaultFilterModule, SharedModule, LooseComponentsModule],
declarations: [VaultComponent, CiphersComponent],
imports: [
VaultRoutingModule,
VaultFilterModule,
SharedModule,
LooseComponentsModule,
OrganizationBadgeModule,
],
declarations: [VaultComponent, VaultItemsComponent],
exports: [VaultComponent],
})
export class VaultModule {}

View File

@@ -15,7 +15,6 @@ import { LoginWithDeviceComponent } from "./accounts/login/login-with-device.com
import { LoginComponent } from "./accounts/login/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";
@@ -69,16 +68,15 @@ const routes: Routes = [
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
{
path: "register",
component: RegisterComponent,
component: TrialInitiationComponent,
canActivate: [UnauthGuard],
data: { titleId: "createAccount" },
},
buildFlaggedRoute("showTrial", {
{
path: "trial",
component: TrialInitiationComponent,
canActivate: [UnauthGuard],
data: { titleId: "startTrial" },
}),
redirectTo: "register",
pathMatch: "full",
},
{
path: "sso",
component: SsoComponent,

View File

@@ -8,6 +8,7 @@ import { PasswordRepromptService } from "@bitwarden/common/abstractions/password
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { BadgeTypes } from "@bitwarden/components";
import { CipherReportComponent } from "./cipher-report.component";
@@ -16,7 +17,7 @@ import { CipherReportComponent } from "./cipher-report.component";
templateUrl: "weak-passwords-report.component.html",
})
export class WeakPasswordsReportComponent extends CipherReportComponent implements OnInit {
passwordStrengthMap = new Map<string, [string, string]>();
passwordStrengthMap = new Map<string, [string, BadgeTypes]>();
private passwordStrengthCache = new Map<string, number>();
@@ -110,7 +111,7 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen
return true;
}
private scoreKey(score: number): [string, string] {
private scoreKey(score: number): [string, BadgeTypes] {
switch (score) {
case 4:
return ["strong", "success"];

View File

@@ -1,5 +1,7 @@
import { Component, Input } from "@angular/core";
import { Icon } from "@bitwarden/components";
import { ReportVariant } from "../models/report-variant";
@Component({
@@ -10,7 +12,7 @@ export class ReportCardComponent {
@Input() title: string;
@Input() description: string;
@Input() route: string;
@Input() icon: string;
@Input() icon: Icon;
@Input() variant: ReportVariant;
protected get disabled() {

View File

@@ -81,7 +81,7 @@
id="text"
rows="8"
name="Text"
[(ngModel)]="sendText"
[ngModel]="sendText"
class="form-control"
readonly
></textarea>

View File

@@ -55,7 +55,7 @@
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="typeChanged(o)"
(change)="typeChanged()"
[checked]="send.type === o.value"
/>
<label class="form-check-label" for="type_{{ o.value }}">
@@ -120,14 +120,7 @@
<h3 class="mt-5">{{ "share" | i18n }}</h3>
<div class="form-group" *ngIf="link">
<label for="link">{{ "sendLinkLabel" | i18n }}</label>
<input
type="text"
readonly
id="link"
name="Link"
[(ngModel)]="link"
class="form-control"
/>
<input type="text" readonly id="link" name="Link" [ngModel]="link" class="form-control" />
</div>
<div class="form-group">
<div class="form-check">
@@ -286,17 +279,17 @@
class="btn btn-outline-danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="deleteBtn.loading"
[disabled]="$any(deleteBtn).loading"
[appApiAction]="deletePromise"
>
<i
class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="deleteBtn.loading"
[hidden]="$any(deleteBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!deleteBtn.loading"
[hidden]="!$any(deleteBtn).loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>

View File

@@ -13,7 +13,6 @@
formControlName="fallbackDeletionDate"
required
placeholder="MM/DD/YYYY"
[readOnly]="disableSend"
data-date-format="mm/dd/yyyy"
/>
<input
@@ -24,7 +23,6 @@
formControlName="fallbackDeletionTime"
required
placeholder="HH:MM AM/PM"
[readOnly]="disableSend"
/>
</div>
</ng-container>
@@ -38,7 +36,6 @@
formControlName="fallbackDeletionDate"
required
placeholder="MM/DD/YYYY"
[readOnly]="disableSend"
data-date-format="mm/dd/yyyy"
/>
<select

View File

@@ -59,7 +59,7 @@
<h1>
{{ "send" | i18n }}
<small #actionSpinner [appApiAction]="actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<ng-container *ngIf="$any(actionSpinner).loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"

View File

@@ -121,16 +121,16 @@
(click)="delete()"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="deleteBtn.loading"
[disabled]="$any(deleteBtn).loading"
>
<i
class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="deleteBtn.loading"
[hidden]="$any(deleteBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!deleteBtn.loading"
[hidden]="!$any(deleteBtn).loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>

View File

@@ -3,7 +3,7 @@ import { Component } from "@angular/core";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
@@ -38,7 +38,7 @@ export class EmergencyAddEditComponent extends BaseAddEditComponent {
totpService: TotpService,
passwordGenerationService: PasswordGenerationService,
messagingService: MessagingService,
eventService: EventService,
eventCollectionService: EventCollectionService,
policyService: PolicyService,
passwordRepromptService: PasswordRepromptService,
organizationService: OrganizationService,
@@ -55,7 +55,7 @@ export class EmergencyAddEditComponent extends BaseAddEditComponent {
totpService,
passwordGenerationService,
messagingService,
eventService,
eventCollectionService,
policyService,
organizationService,
logService,

View File

@@ -240,14 +240,6 @@
{{ "monthAbbr" | i18n }} =
{{ additionalStorageTotal(selectablePlan) | currency: "$" }} /{{ "year" | i18n }}
</small>
<small *ngIf="selectablePlan.hasPremiumAccessOption && premiumAccessAddon">
{{ "premiumAccess" | i18n }}:
{{ selectablePlan.premiumAccessOptionCost / 12 | currency: "$" }} &times; 12
{{ "monthAbbr" | i18n }}
=
{{ 40 | currency: "$" }}
/{{ "year" | i18n }}
</small>
</ng-container>
<ng-container *ngIf="!selectablePlan.isAnnual">
{{ "monthly" | i18n }}
@@ -272,17 +264,6 @@
{{ "monthAbbr" | i18n }} =
{{ additionalStorageTotal(selectablePlan) | currency: "$" }} /{{ "month" | i18n }}
</small>
<small
*ngIf="
selectablePlan.hasPremiumAccessOption &&
formGroup.controls['premiumAccessAddon'].value
"
>
{{ "premiumAccess" | i18n }}:
{{ selectablePlan.premiumAccessOptionCost | currency: "$" }} {{ "monthAbbr" | i18n }} =
{{ 40 | currency: "$" }}
/{{ "month" | i18n }}
</small>
</ng-container>
</label>
</div>

View File

@@ -121,7 +121,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
}
if (this.createOrganization) {
this.formGroup.controls.name.addValidators(Validators.required);
this.formGroup.controls.name.addValidators([Validators.required, Validators.maxLength(50)]);
this.formGroup.controls.billingEmail.addValidators(Validators.required);
}

View File

@@ -25,7 +25,7 @@
*ngIf="!isSelfHosted && !sponsoringOrg.familySponsorshipValidUntil"
[appApiAction]="resendEmailPromise"
class="dropdown-item btn-submit"
[disabled]="resendEmailBtn.loading"
[disabled]="$any(resendEmailBtn).loading"
(click)="resendEmail()"
[attr.aria-label]="'resendEmailLabel' | i18n: sponsoringOrg.familySponsorshipFriendlyName"
>
@@ -36,7 +36,7 @@
#revokeSponsorshipBtn
[appApiAction]="revokeSponsorshipPromise"
class="dropdown-item text-danger btn-submit"
[disabled]="revokeSponsorshipBtn.loading"
[disabled]="$any(revokeSponsorshipBtn).loading"
(click)="revokeSponsorship()"
[attr.aria-label]="'revokeAccount' | i18n: sponsoringOrg.familySponsorshipFriendlyName"
>

View File

@@ -18,7 +18,7 @@
<app-two-factor-verify
[organizationId]="organizationId"
[type]="type"
(onAuthed)="auth($event)"
(onAuthed)="auth($any($event))"
*ngIf="!authed"
>
</app-two-factor-verify>

View File

@@ -18,7 +18,7 @@
<app-two-factor-verify
[organizationId]="organizationId"
[type]="type"
(onAuthed)="auth($event)"
(onAuthed)="auth($any($event))"
*ngIf="!authed"
>
</app-two-factor-verify>

View File

@@ -18,7 +18,7 @@
<app-two-factor-verify
[organizationId]="organizationId"
[type]="type"
(onAuthed)="auth($event)"
(onAuthed)="auth($any($event))"
*ngIf="!authed"
>
</app-two-factor-verify>
@@ -61,7 +61,7 @@
class="btn btn-outline-primary btn-sm btn-submit align-self-start"
(click)="sendEmail()"
[appApiAction]="emailPromise"
[disabled]="sendBtn.loading"
[disabled]="$any(sendBtn).loading"
>
<i
class="bwi bwi-spinner bwi-spin"

View File

@@ -15,12 +15,7 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<app-two-factor-verify
[organizationId]="organizationId"
[type]="type"
(onAuthed)="auth($event)"
*ngIf="!authed"
>
<app-two-factor-verify [type]="type" (onAuthed)="auth($event)" *ngIf="!authed">
</app-two-factor-verify>
<ng-container *ngIf="authed">
<div class="modal-body text-center">

View File

@@ -18,7 +18,7 @@
<app-two-factor-verify
[organizationId]="organizationId"
[type]="type"
(onAuthed)="auth($event)"
(onAuthed)="auth($any($event))"
*ngIf="!authed"
>
</app-two-factor-verify>
@@ -54,7 +54,7 @@
<i class="bwi bwi-li bwi-key"></i>
<strong *ngIf="!k.configured || !k.name">{{ "webAuthnkeyX" | i18n: i + 1 }}</strong>
<strong *ngIf="k.configured && k.name">{{ k.name }}</strong>
<ng-container *ngIf="k.configured && !removeKeyBtn.loading">
<ng-container *ngIf="k.configured && !$any(removeKeyBtn).loading">
<ng-container *ngIf="k.migrated">
<span>{{ "webAuthnMigrated" | i18n }}</span>
</ng-container>
@@ -63,7 +63,7 @@
<i
class="bwi bwi-spin bwi-spinner text-muted bwi-fw"
title="{{ 'loading' | i18n }}"
*ngIf="removeKeyBtn.loading"
*ngIf="$any(removeKeyBtn).loading"
aria-hidden="true"
></i>
-
@@ -96,16 +96,16 @@
type="button"
(click)="readKey()"
class="btn btn-outline-secondary mr-2"
[disabled]="readKeyBtn.loading || webAuthnListening || !keyIdAvailable"
[disabled]="$any(readKeyBtn).loading || webAuthnListening || !keyIdAvailable"
#readKeyBtn
[appApiAction]="challengePromise"
>
{{ "readKey" | i18n }}
</button>
<ng-container *ngIf="readKeyBtn.loading">
<ng-container *ngIf="$any(readKeyBtn).loading">
<i class="bwi bwi-spinner bwi-spin text-muted" aria-hidden="true"></i>
</ng-container>
<ng-container *ngIf="!readKeyBtn.loading">
<ng-container *ngIf="!$any(readKeyBtn).loading">
<ng-container *ngIf="webAuthnListening">
<i class="bwi bwi-spinner bwi-spin text-muted" aria-hidden="true"></i>
{{ "twoFactorU2fWaiting" | i18n }}...
@@ -138,8 +138,7 @@
#disableBtn
type="button"
class="btn btn-outline-secondary btn-submit"
[appApiAction]="disablePromise"
[disabled]="disableBtn.loading"
[disabled]="$any(disableBtn).loading"
(click)="disable()"
*ngIf="enabled"
>

View File

@@ -18,7 +18,7 @@
<app-two-factor-verify
[organizationId]="organizationId"
[type]="type"
(onAuthed)="auth($event)"
(onAuthed)="auth($any($event))"
*ngIf="!authed"
>
</app-two-factor-verify>
@@ -104,7 +104,7 @@
type="button"
class="btn btn-outline-secondary btn-submit"
[appApiAction]="disablePromise"
[disabled]="disableBtn.loading"
[disabled]="$any(disableBtn).loading"
(click)="disable()"
*ngIf="enabled"
>

View File

@@ -46,7 +46,7 @@
class="btn-submit"
(click)="reinstate()"
[appApiAction]="reinstatePromise"
[disabled]="reinstateBtn.loading"
[disabled]="$any(reinstateBtn).loading"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "reinstateSubscription" | i18n }}</span>
@@ -147,7 +147,7 @@
class="btn-submit tw-ml-auto"
(click)="cancel()"
[appApiAction]="cancelPromise"
[disabled]="cancelBtn.loading"
[disabled]="$any(cancelBtn).loading"
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>

View File

@@ -9,7 +9,7 @@
class="btn btn-block btn-outline-secondary btn-submit"
#sendBtn
[appApiAction]="actionPromise"
[disabled]="sendBtn.loading"
[disabled]="$any(sendBtn).loading"
(click)="send()"
>
<i class="bwi bwi-spin bwi-spinner" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>

View File

@@ -1,7 +1,5 @@
import { NgModule } from "@angular/core";
import { UserVerificationComponent } from "@bitwarden/angular/components/user-verification.component";
import { AcceptEmergencyComponent } from "../accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "../accounts/accept-organization.component";
import { HintComponent } from "../accounts/hint.component";
@@ -9,7 +7,6 @@ import { LockComponent } from "../accounts/lock.component";
import { RecoverDeleteComponent } from "../accounts/recover-delete.component";
import { RecoverTwoFactorComponent } from "../accounts/recover-two-factor.component";
import { RegisterFormModule } from "../accounts/register-form/register-form.module";
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";
@@ -23,6 +20,7 @@ import { OrganizationSwitcherComponent } from "../components/organization-switch
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
import { PremiumBadgeComponent } from "../components/premium-badge.component";
import { UserVerificationPromptComponent } from "../components/user-verification-prompt.component";
import { UserVerificationComponent } from "../components/user-verification.component";
import { FooterComponent } from "../layouts/footer.component";
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
import { NavbarComponent } from "../layouts/navbar.component";
@@ -118,7 +116,6 @@ import { BulkRestoreComponent } from "../vault/bulk-restore.component";
import { BulkShareComponent } from "../vault/bulk-share.component";
import { CollectionsComponent } from "../vault/collections.component";
import { FolderAddEditComponent } from "../vault/folder-add-edit.component";
import { OrganizationBadgeModule } from "../vault/organization-badge/organization-badge.module";
import { ShareComponent } from "../vault/share.component";
import { SharedModule } from "./shared.module";
@@ -126,7 +123,7 @@ import { SharedModule } from "./shared.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, OrganizationBadgeModule, OrganizationCreateModule, RegisterFormModule],
imports: [SharedModule, OrganizationCreateModule, RegisterFormModule],
declarations: [
PremiumBadgeComponent,
AcceptEmergencyComponent,
@@ -207,7 +204,6 @@ import { SharedModule } from "./shared.module";
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
SecurityComponent,
SecurityKeysComponent,
@@ -324,7 +320,6 @@ import { SharedModule } from "./shared.module";
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
SecurityComponent,
SecurityKeysComponent,

View File

@@ -21,6 +21,7 @@ import {
LinkModule,
MenuModule,
MultiSelectModule,
NavigationModule,
TableModule,
TabsModule,
} from "@bitwarden/components";
@@ -40,52 +41,60 @@ import "./locales";
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
InfiniteScrollModule,
RouterModule,
BadgeModule,
ButtonModule,
CalloutModule,
ToastrModule,
JslibModule,
// Component library
AsyncActionsModule,
AvatarModule,
BadgeModule,
BadgeListModule,
ButtonModule,
MenuModule,
CalloutModule,
DialogModule,
MultiSelectModule,
FormFieldModule,
IconModule,
TabsModule,
TableModule,
AvatarModule,
IconButtonModule,
LinkModule,
DialogModule,
IconModule,
MenuModule,
NavigationModule,
TableModule,
TabsModule,
// Web specific
],
exports: [
CommonModule,
AsyncActionsModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
InfiniteScrollModule,
RouterModule,
ToastrModule,
JslibModule,
// Component library
AsyncActionsModule,
AvatarModule,
BadgeModule,
BadgeListModule,
ButtonModule,
CalloutModule,
ToastrModule,
MenuModule,
DialogModule,
MultiSelectModule,
FormFieldModule,
IconModule,
TabsModule,
TableModule,
AvatarModule,
IconButtonModule,
IconModule,
MenuModule,
NavigationModule,
TableModule,
LinkModule,
DialogModule,
TabsModule,
// Web specific
],
providers: [DatePipe],
bootstrap: [],

View File

@@ -266,23 +266,19 @@
</div>
</div>
<ng-container *ngIf="usernameOptions.type === 'forwarded'">
<div class="form-group">
<div class="form-group" role="listbox">
<label class="d-block">{{ "service" | i18n }}</label>
<div class="form-check" *ngFor="let o of forwardOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="usernameOptions.forwardedService"
name="ForwardType"
id="forwardtype_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.forwardedService === o.value"
/>
<label class="form-check-label" for="forwardtype_{{ o.value }}">
<select
id="ForwardTypeDropdown"
name="ForwardType"
[(ngModel)]="usernameOptions.forwardedService"
(change)="saveUsernameOptions()"
class="form-control w-auto"
>
<option *ngFor="let o of forwardOptions" [ngValue]="o.value" role="option">
{{ o.name }}
</label>
</div>
</option>
</select>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'simplelogin'">
<div class="form-group col-4">
@@ -303,7 +299,7 @@
id="duckduckgo-apikey"
class="form-control"
type="password"
name="DuckDudkGoApiKey"
name="DuckDuckGoApiKey"
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
(blur)="saveUsernameOptions()"
/>
@@ -412,7 +408,7 @@
type="button"
class="btn btn-submit btn-primary"
(click)="regenerate()"
[disabled]="form.loading"
[disabled]="$any(form).loading"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "regenerateUsername" | i18n }}</span>

View File

@@ -89,7 +89,7 @@
<bit-label>{{ "filePassword" | i18n }}</bit-label>
<input
bitInput
type="{{ showFilePassword ? 'text' : 'password' }}"
[type]="showFilePassword ? 'text' : 'password'"
id="filePassword"
formControlName="filePassword"
name="password"
@@ -123,7 +123,7 @@
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
<input
bitInput
type="{{ showConfirmFilePassword ? 'text' : 'password' }}"
[type]="showConfirmFilePassword ? 'text' : 'password'"
id="confirmFilePassword"
formControlName="confirmFilePassword"
name="confirmFilePassword"
@@ -157,7 +157,8 @@
<button
type="submit"
class="btn btn-primary btn-submit"
[disabled]="form.loading || disabled"
[disabled]="form.loading || disabledByPolicy"
[ngClass]="{ manual: disabledByPolicy }"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "confirmFormat" | i18n }}</span>

View File

@@ -4,7 +4,7 @@ import { UntypedFormBuilder } from "@angular/forms";
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/components/export.component";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { ExportService } from "@bitwarden/common/abstractions/export.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -29,7 +29,7 @@ export class ExportComponent extends BaseExportComponent {
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
exportService: ExportService,
eventService: EventService,
eventCollectionService: EventCollectionService,
policyService: PolicyService,
logService: LogService,
userVerificationService: UserVerificationService,
@@ -42,7 +42,7 @@ export class ExportComponent extends BaseExportComponent {
i18nService,
platformUtilsService,
exportService,
eventService,
eventCollectionService,
policyService,
window,
logService,

View File

@@ -19,7 +19,7 @@
<input
bitInput
required
type="{{ showFilePassword ? 'text' : 'password' }}"
[type]="showFilePassword ? 'text' : 'password'"
name="filePassword"
[formControl]="filePassword"
appAutofocus

View File

@@ -80,11 +80,13 @@
right) and select "Settings". Go to "Export" and find the "Export to .csv File" option. Click
"Export" to save the CSV file.
</ng-container>
<!--
<ng-container *ngIf="format === 'keeperjson'">
Log into the Keeper web vault (keepersecurity.com/vault). Click on your "account email" (top
right) and select "Settings". Go to "Export" and find the "Export to .json File" option. Click
"Export" to save the JSON file.
</ng-container>
-->
<ng-container
*ngIf="format === 'chromecsv' || format === 'operacsv' || format === 'vivaldicsv'"
>

View File

@@ -122,8 +122,8 @@
[(ngModel)]="f.value"
*ngIf="f.type === fieldType.Boolean"
appTrueFalseValue
trueValue="true"
falseValue="false"
[trueValue]="true"
[falseValue]="false"
[disabled]="cipher.isDeleted || viewOnly"
attr.aria-describedby="fieldName{{ i }}"
/>

View File

@@ -1,7 +1,7 @@
import { Component, Input } from "@angular/core";
import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "@bitwarden/angular/components/add-edit-custom-fields.component";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@Component({
@@ -12,7 +12,7 @@ export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsCompone
@Input() viewOnly: boolean;
@Input() copy: (value: string, typeI18nKey: string, aType: string) => void;
constructor(i18nService: I18nService, eventService: EventService) {
super(i18nService, eventService);
constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) {
super(i18nService, eventCollectionService);
}
}

View File

@@ -118,13 +118,13 @@
>
<i
class="bwi bwi-lg bwi-fw bwi-check-circle"
[hidden]="checkPasswordBtn.loading"
[hidden]="$any(checkPasswordBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-lg bwi-fw bwi-spinner bwi-spin"
aria-hidden="true"
[hidden]="!checkPasswordBtn.loading"
[hidden]="!$any(checkPasswordBtn).loading"
title="{{ 'loading' | i18n }}"
></i>
</a>
@@ -838,7 +838,7 @@
<input
class="form-check-input"
type="checkbox"
[(ngModel)]="c.checked"
[(ngModel)]="$any(c).checked"
id="collection-{{ i }}"
name="Collection[{{ i }}].Checked"
[disabled]="cipher.isDeleted || viewOnly"
@@ -935,17 +935,17 @@
class="btn btn-outline-danger"
appA11yTitle="{{ (cipher.isDeleted ? 'permanentlyDelete' : 'delete') | i18n }}"
*ngIf="editMode && !cloneMode && !(!cipher.edit && editMode)"
[disabled]="deleteBtn.loading"
[disabled]="$any(deleteBtn).loading"
[appApiAction]="deletePromise"
>
<i
class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="deleteBtn.loading"
[hidden]="$any(deleteBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!deleteBtn.loading"
[hidden]="!$any(deleteBtn).loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>

View File

@@ -4,7 +4,7 @@ import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/com
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
@@ -49,7 +49,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
protected totpService: TotpService,
protected passwordGenerationService: PasswordGenerationService,
protected messagingService: MessagingService,
eventService: EventService,
eventCollectionService: EventCollectionService,
protected policyService: PolicyService,
organizationService: OrganizationService,
logService: LogService,
@@ -64,7 +64,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
stateService,
collectionService,
messagingService,
eventService,
eventCollectionService,
policyService,
logService,
passwordRepromptService,
@@ -125,11 +125,17 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
if (this.editMode) {
if (typeI18nKey === "password") {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipherId);
this.eventCollectionService.collect(
EventType.Cipher_ClientToggledHiddenFieldVisible,
this.cipherId
);
} else if (typeI18nKey === "securityCode") {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
} else if (aType === "H_Field") {
this.eventService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId);
this.eventCollectionService.collect(
EventType.Cipher_ClientCopiedHiddenField,
this.cipherId
);
}
}
}

View File

@@ -26,10 +26,14 @@
<tbody>
<tr *ngFor="let a of cipher.attachments">
<td class="table-list-icon">
<i class="bwi bwi-fw bwi-lg bwi-file" *ngIf="!a.downloading" aria-hidden="true"></i>
<i
class="bwi bwi-fw bwi-lg bwi-file"
*ngIf="!$any(a).downloading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-lg bwi-fw bwi-spin"
*ngIf="a.downloading"
*ngIf="$any(a).downloading"
aria-hidden="true"
></i>
</td>
@@ -55,7 +59,7 @@
(click)="reupload(a)"
#reuploadBtn
[appApiAction]="reuploadPromises[a.id]"
[disabled]="reuploadBtn.loading"
[disabled]="$any(reuploadBtn).loading"
>
{{ "fix" | i18n }}
</button>
@@ -72,16 +76,16 @@
(click)="delete(a)"
#deleteBtn
[appApiAction]="deletePromises[a.id]"
[disabled]="deleteBtn.loading"
[disabled]="$any(deleteBtn).loading"
>
<i
class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="deleteBtn.loading"
[hidden]="$any(deleteBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!deleteBtn.loading"
[hidden]="!$any(deleteBtn).loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>

View File

@@ -11,7 +11,7 @@ import { BulkDeleteComponent } from "./bulk-delete.component";
import { BulkMoveComponent } from "./bulk-move.component";
import { BulkRestoreComponent } from "./bulk-restore.component";
import { BulkShareComponent } from "./bulk-share.component";
import { CiphersComponent } from "./ciphers.component";
import { VaultItemsComponent } from "./vault-items.component";
@Component({
selector: "app-vault-bulk-actions",
@@ -19,7 +19,7 @@ import { CiphersComponent } from "./ciphers.component";
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class BulkActionsComponent {
@Input() ciphersComponent: CiphersComponent;
@Input() vaultItemsComponent: VaultItemsComponent;
@Input() deleted: boolean;
@Input() organization: Organization;
@@ -44,7 +44,7 @@ export class BulkActionsComponent {
return;
}
const selectedIds = this.ciphersComponent.getSelectedIds();
const selectedIds = this.vaultItemsComponent.getSelectedIds();
if (selectedIds.length === 0) {
this.platformUtilsService.showToast(
"error",
@@ -64,7 +64,7 @@ export class BulkActionsComponent {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onDeleted.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
}
);
@@ -75,7 +75,7 @@ export class BulkActionsComponent {
return;
}
const selectedIds = this.ciphersComponent.getSelectedIds();
const selectedIds = this.vaultItemsComponent.getSelectedIds();
if (selectedIds.length === 0) {
this.platformUtilsService.showToast(
"error",
@@ -93,7 +93,7 @@ export class BulkActionsComponent {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onRestored.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
}
);
@@ -104,7 +104,7 @@ export class BulkActionsComponent {
return;
}
const selectedCiphers = this.ciphersComponent.getSelected();
const selectedCiphers = this.vaultItemsComponent.getSelected();
if (selectedCiphers.length === 0) {
this.platformUtilsService.showToast(
"error",
@@ -122,7 +122,7 @@ export class BulkActionsComponent {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onShared.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
}
);
@@ -133,7 +133,7 @@ export class BulkActionsComponent {
return;
}
const selectedIds = this.ciphersComponent.getSelectedIds();
const selectedIds = this.vaultItemsComponent.getSelectedIds();
if (selectedIds.length === 0) {
this.platformUtilsService.showToast(
"error",
@@ -151,18 +151,18 @@ export class BulkActionsComponent {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onMoved.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
}
);
}
selectAll(select: boolean) {
this.ciphersComponent.selectAll(select);
this.vaultItemsComponent.selectAll(select);
}
private async promptPassword() {
const selectedCiphers = this.ciphersComponent.getSelected();
const selectedCiphers = this.vaultItemsComponent.getSelected();
const notProtected = !selectedCiphers.find(
(cipher) => cipher.reprompt !== CipherRepromptType.None
);

View File

@@ -54,7 +54,7 @@
<td class="table-list-checkbox">
<input
type="checkbox"
[(ngModel)]="c.checked"
[(ngModel)]="$any(c).checked"
name="Collection[{{ i }}].Checked"
appStopProp
/>

View File

@@ -37,7 +37,7 @@
<td class="table-list-checkbox">
<input
type="checkbox"
[(ngModel)]="c.checked"
[(ngModel)]="$any(c).checked"
name="Collection[{{ i }}].Checked"
appStopProp
/>

View File

@@ -46,17 +46,17 @@
class="btn btn-outline-danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="deleteBtn.loading"
[disabled]="$any(deleteBtn).loading"
[appApiAction]="deletePromise"
>
<i
class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="deleteBtn.loading"
[hidden]="$any(deleteBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!deleteBtn.loading"
[hidden]="!$any(deleteBtn).loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>

View File

@@ -10,7 +10,7 @@
<tbody>
<tr *ngFor="let c of filteredCiphers">
<td (click)="checkCipher(c)" class="table-list-checkbox">
<input type="checkbox" [(ngModel)]="c.checked" appStopProp />
<input type="checkbox" [(ngModel)]="$any(c).checked" appStopProp />
</td>
<td (click)="checkCipher(c)" class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>

View File

@@ -1,8 +1,8 @@
import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core";
import { CiphersComponent as BaseCiphersComponent } from "@bitwarden/angular/components/ciphers.component";
import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/components/vault-items.component";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
@@ -22,10 +22,10 @@ import { Icons } from "@bitwarden/components";
const MaxCheckedCount = 500;
@Component({
selector: "app-vault-ciphers",
templateUrl: "ciphers.component.html",
selector: "app-vault-items",
templateUrl: "vault-items.component.html",
})
export class CiphersComponent extends BaseCiphersComponent implements OnDestroy {
export class VaultItemsComponent extends BaseVaultItemsComponent implements OnDestroy {
@Input() showAddNew = true;
@Output() onAttachmentsClicked = new EventEmitter<CipherView>();
@Output() onShareClicked = new EventEmitter<CipherView>();
@@ -51,7 +51,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected cipherService: CipherService,
protected eventService: EventService,
protected eventCollectionService: EventCollectionService,
protected totpService: TotpService,
protected stateService: StateService,
protected passwordRepromptService: PasswordRepromptService,
@@ -241,9 +241,12 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
);
if (typeI18nKey === "password" || typeI18nKey === "verificationCodeTotp") {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id);
this.eventCollectionService.collect(
EventType.Cipher_ClientToggledHiddenFieldVisible,
cipher.id
);
} else if (typeI18nKey === "securityCode") {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
}
}
@@ -289,6 +292,10 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
this.onOrganzationBadgeClicked.emit(organizationId);
}
events(c: CipherView) {
// TODO: This should be removed but is needed since we reuse the same template
}
protected deleteCipher(id: string, permanent: boolean) {
return permanent
? this.cipherService.deleteWithServer(id)

View File

@@ -20,8 +20,8 @@
<div class="page-header d-flex">
<h1>
{{ "vaultItems" | i18n }}
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<small #actionSpinner [appApiAction]="vaultItemsComponent.actionPromise">
<ng-container *ngIf="$any(actionSpinner).loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
@@ -33,7 +33,7 @@
</h1>
<div class="ml-auto d-flex">
<app-vault-bulk-actions
[ciphersComponent]="ciphersComponent"
[vaultItemsComponent]="vaultItemsComponent"
[deleted]="activeFilter.isDeleted"
>
</app-vault-bulk-actions>
@@ -50,7 +50,7 @@
<app-callout type="warning" *ngIf="activeFilter.isDeleted" icon="bwi-exclamation-triangle">
{{ trashCleanupWarning }}
</app-callout>
<app-vault-ciphers
<app-vault-items
(onCipherClicked)="editCipher($event)"
(onAttachmentsClicked)="editCipherAttachments($event)"
(onAddCipher)="addCipher()"
@@ -59,7 +59,7 @@
(onCloneClicked)="cloneCipher($event)"
(onOrganzationBadgeClicked)="applyOrganizationFilter($event)"
>
</app-vault-ciphers>
</app-vault-items>
</div>
<div class="col-3">
<div class="card border-warning mb-4" *ngIf="showUpdateKey">

View File

@@ -31,7 +31,6 @@ 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 { ShareComponent } from "./share.component";
@@ -39,6 +38,7 @@ import { VaultFilterComponent } from "./vault-filter/components/vault-filter.com
import { VaultFilterService } from "./vault-filter/services/abstractions/vault-filter.service";
import { VaultFilter } from "./vault-filter/shared/models/vault-filter.model";
import { FolderFilter, OrganizationFilter } from "./vault-filter/shared/models/vault-filter.type";
import { VaultItemsComponent } from "./vault-items.component";
const BroadcasterSubscriptionId = "VaultComponent";
@@ -48,7 +48,7 @@ const BroadcasterSubscriptionId = "VaultComponent";
})
export class VaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent;
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
@ViewChild(VaultItemsComponent, { static: true }) vaultItemsComponent: VaultItemsComponent;
@ViewChild("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
@@ -118,7 +118,7 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.editCipher(cipherView);
}
}
await this.ciphersComponent.reload();
await this.vaultItemsComponent.reload();
/* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */
this.route.queryParams.subscribe(async (params) => {
@@ -147,7 +147,7 @@ export class VaultComponent implements OnInit, OnDestroy {
if (message.successfully) {
await Promise.all([
this.vaultFilterService.reloadCollections(),
this.ciphersComponent.load(this.ciphersComponent.filter),
this.vaultItemsComponent.load(this.vaultItemsComponent.filter),
]);
this.changeDetectorRef.detectChanges();
}
@@ -177,8 +177,8 @@ export class VaultComponent implements OnInit, OnDestroy {
async applyVaultFilter(filter: VaultFilter) {
this.activeFilter = filter;
this.ciphersComponent.showAddNew = !this.activeFilter.isDeleted;
await this.ciphersComponent.reload(
this.vaultItemsComponent.showAddNew = !this.activeFilter.isDeleted;
await this.vaultItemsComponent.reload(
this.activeFilter.buildFilter(),
this.activeFilter.isDeleted
);
@@ -227,8 +227,8 @@ export class VaultComponent implements OnInit, OnDestroy {
};
filterSearchText(searchText: string) {
this.ciphersComponent.searchText = searchText;
this.ciphersComponent.search(200);
this.vaultItemsComponent.searchText = searchText;
this.vaultItemsComponent.search(200);
}
async editCipherAttachments(cipher: CipherView) {
@@ -264,7 +264,7 @@ export class VaultComponent implements OnInit, OnDestroy {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
modal.onClosed.subscribe(async () => {
if (madeAttachmentChanges) {
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
}
madeAttachmentChanges = false;
});
@@ -279,7 +279,7 @@ export class VaultComponent implements OnInit, OnDestroy {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onSharedCipher.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
}
);
@@ -294,7 +294,7 @@ export class VaultComponent implements OnInit, OnDestroy {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onSavedCollections.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
}
);
@@ -337,17 +337,17 @@ export class VaultComponent implements OnInit, OnDestroy {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onSavedCipher.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onDeletedCipher.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onRestoredCipher.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
await this.vaultItemsComponent.refresh();
});
}
);

View File

@@ -2,10 +2,10 @@ import { NgModule } from "@angular/core";
import { SharedModule, LooseComponentsModule } from "../shared";
import { CiphersComponent } from "./ciphers.component";
import { OrganizationBadgeModule } from "./organization-badge/organization-badge.module";
import { PipesModule } from "./pipes/pipes.module";
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
import { VaultItemsComponent } from "./vault-items.component";
import { VaultRoutingModule } from "./vault-routing.module";
import { VaultComponent } from "./vault.component";
@@ -18,7 +18,7 @@ import { VaultComponent } from "./vault.component";
SharedModule,
LooseComponentsModule,
],
declarations: [VaultComponent, CiphersComponent],
declarations: [VaultComponent, VaultItemsComponent],
exports: [VaultComponent],
})
export class VaultModule {}

View File

@@ -2444,7 +2444,7 @@
"message": "Eienaar"
},
"ownerDesc": {
"message": "The highest access user that can manage all aspects of your organization."
"message": "Manage all aspects of your organization, including billing and subscriptions"
},
"clientOwnerDesc": {
"message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization."
@@ -2453,13 +2453,13 @@
"message": "Admin"
},
"adminDesc": {
"message": "Admins can access and manage all items, collections and users in your organization."
"message": "Manage organization access, all collections, members, reporting, and security settings"
},
"user": {
"message": "Gebruiker"
},
"userDesc": {
"message": "A regular user with access to assigned collections in your organization."
"message": "Access and add items to assigned collections"
},
"manager": {
"message": "Bestuurder"
@@ -4117,7 +4117,22 @@
"message": "Pasgemaak"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
"message": "Grant customized permissions to members"
},
"customDescNonEnterpriseStart": {
"message": "Custom roles is an ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "enterprise feature",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Contact our support team to upgrade your subscription",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "To enable custom permissions the organization must be on an Enterprise 2020 plan."
},
"permissions": {
"message": "Toestemmings"

View File

@@ -122,16 +122,16 @@
"message": "كانون الأول"
},
"title": {
"message": "Title"
"message": "اللقب"
},
"mr": {
"message": "Mr"
"message": "السيد"
},
"mrs": {
"message": "Mrs"
"message": "السيدة"
},
"ms": {
"message": "Ms"
"message": "الآنسة"
},
"dr": {
"message": "د"
@@ -152,7 +152,7 @@
"message": "حقل مخصص جديد"
},
"value": {
"message": "Value"
"message": "القيمة"
},
"dragToSort": {
"message": "اسحب للفرز"
@@ -164,7 +164,7 @@
"message": "مخفي"
},
"cfTypeBoolean": {
"message": "Boolean"
"message": "قيمة منطقية"
},
"cfTypeLinked": {
"message": "Linked",
@@ -174,7 +174,7 @@
"message": "إزالة"
},
"unassigned": {
"message": "Unassigned"
"message": "غير معين"
},
"noneFolder": {
"message": "لا يوجد مجلد",
@@ -233,7 +233,7 @@
"message": "Check if password has been exposed."
},
"passwordExposed": {
"message": "This password has been exposed $VALUE$ time(s) in data breaches. You should change it.",
"message": "تم كشف كلمة المرور هذه {0} مرة (مرات) في عمليات اختراق البيانات. يجب عليك تغييرها.",
"placeholders": {
"value": {
"content": "$1",
@@ -242,7 +242,7 @@
}
},
"passwordSafe": {
"message": "This password was not found in any known data breaches. It should be safe to use."
"message": "لم يتم العثور على كلمة المرور هذه في أي عمليات اختراق معروفة للبيانات. من المفترض أن تكون آمنة للاستخدام."
},
"save": {
"message": "حفظ"
@@ -269,16 +269,16 @@
"message": "تعديل"
},
"searchCollection": {
"message": "Search collection"
"message": "ابحث في المجموعة"
},
"searchFolder": {
"message": "Search folder"
"message": "ابحث في المجلّد"
},
"searchFavorites": {
"message": "بحث في المفضلة"
},
"searchType": {
"message": "Search type",
"message": "نوع البحث",
"description": "Search item type"
},
"searchVault": {
@@ -321,7 +321,7 @@
"message": "المجلدات"
},
"collections": {
"message": "Collections"
"message": "المجموعات"
},
"firstName": {
"message": "الاسم الأول"
@@ -357,7 +357,7 @@
"message": "الدولة"
},
"shared": {
"message": "Shared"
"message": "مشترك"
},
"attachments": {
"message": "المرفقات"
@@ -375,7 +375,7 @@
"message": "عرض العنصر"
},
"ex": {
"message": "ex.",
"message": "مثال.",
"description": "Short abbreviation for 'example'."
},
"other": {
@@ -388,7 +388,7 @@
"message": "نقل إلى مؤسسة"
},
"valueCopied": {
"message": "$VALUE$ copied",
"message": "تم نسخ $VALUE$",
"description": "Value has been copied to the clipboard.",
"placeholders": {
"value": {
@@ -428,7 +428,7 @@
"message": "خزنتي"
},
"allVaults": {
"message": "All vaults"
"message": "جميع الخزنات"
},
"vault": {
"message": "الخزنة"
@@ -479,7 +479,7 @@
"message": "الحجم الأقصى للملف هو 500 ميجابايت."
},
"updateKey": {
"message": "You cannot use this feature until you update your encryption key."
"message": "لا يمكنك استخدام هذه المِيزة حتى تحديث مفتاح التشفير الخاص بك."
},
"addedItem": {
"message": "تمت إضافة العنصر"
@@ -501,7 +501,7 @@
}
},
"movedItemsToOrg": {
"message": "Selected items moved to $ORGNAME$",
"message": "تم نقل العناصر المحددة إلى $ORGNAME$",
"placeholders": {
"orgname": {
"content": "$1",
@@ -519,7 +519,7 @@
"message": "حذف المرفق"
},
"deleteItemConfirmation": {
"message": "Do you really want to send to the trash?"
"message": "هل تريد فعلًا أن تحذف؟"
},
"deletedItem": {
"message": "تم حذف العنصر"
@@ -531,13 +531,13 @@
"message": "تم نقل العناصر"
},
"overwritePasswordConfirmation": {
"message": "Are you sure you want to overwrite the current password?"
"message": "هل أنت متأكد من أنك تريد الكتابة فوق كلمة المرور الحالية؟"
},
"editedFolder": {
"message": "تم تعديل المجلد"
},
"addedFolder": {
"message": "Folder added"
"message": "تمت إضافة المجلد"
},
"deleteFolderConfirmation": {
"message": "هل أنت متأكد من حذف هذا المجلد؟"
@@ -546,13 +546,13 @@
"message": "تم حذف المجلد"
},
"loggedOut": {
"message": "Logged out"
"message": "تم تسجيل الخروج"
},
"loginExpired": {
"message": "Your login session has expired."
"message": "انتهت صَلاحِيَة جَلسة الدخول."
},
"logOutConfirmation": {
"message": "Are you sure you want to log out?"
"message": "هل أنت متأكد من أنك تريد تسجيل الخروج؟"
},
"logOut": {
"message": "تسجيل الخروج"
@@ -567,31 +567,31 @@
"message": "لا"
},
"loginOrCreateNewAccount": {
"message": "Log in or create a new account to access your secure vault."
"message": "قم بتسجيل الدخول أو أنشئ حساباً جديداً لتتمكن من الوصول إلى خزنتك السرية."
},
"loginWithDevice": {
"message": "Log in with device"
"message": "تسجيل الدخول باستخدام جهاز"
},
"loginWithDeviceEnabledInfo": {
"message": "Log in with device must be set up in the settings of the Bitwarden mobile app. Need another option?"
"message": "تسجيل الدخول باستخدام الجهاز يجب أن يكون مفعلاً في إعدادات تطبيق بيتواردن على هاتفك. هل تحتاج إلى خِيار آخر؟"
},
"loginWithMasterPassword": {
"message": "Log in with master password"
"message": "تسجيل الدخول باستخدام كلمة المرور الرئيسية"
},
"createAccount": {
"message": "إنشاء حساب"
},
"newAroundHere": {
"message": "New around here?"
"message": "هل أنت جديد هنا؟"
},
"startTrial": {
"message": "Start trial"
"message": "ابدأ تجرِبة مجانية"
},
"logIn": {
"message": "تسجيل الدخول"
},
"logInInitiated": {
"message": "Log in initiated"
"message": "بَدْء تسجيل الدخول"
},
"submit": {
"message": "قدِّم"
@@ -603,7 +603,7 @@
"message": "اسمك"
},
"yourNameDesc": {
"message": "What should we call you?"
"message": "ماذا تحب أن ندعوك؟"
},
"masterPass": {
"message": "كلمة المرور الرئيسية"
@@ -645,10 +645,10 @@
"message": "عنوان البريد الإلكتروني غير صالح."
},
"masterPasswordRequired": {
"message": "Master password is required."
"message": "كلمة المرور الرئيسية مطلوبة."
},
"confirmMasterPasswordRequired": {
"message": "Master password retype is required."
"message": "مطلوب إعادة إدخال كلمة المرور الرئيسية."
},
"masterPasswordMinlength": {
"message": "Master password must be at least 8 characters long."
@@ -700,19 +700,19 @@
"message": "قفل الآن"
},
"noItemsInList": {
"message": "There are no items to list."
"message": "لا توجد عناصر لعرضها."
},
"noCollectionsInList": {
"message": "There are no collections to list."
},
"noGroupsInList": {
"message": "There are no groups to list."
"message": "لا توجد أية مجموعات لعرضها."
},
"noUsersInList": {
"message": "There are no users to list."
"message": "ليس هناك مستخدمون لعرضهم."
},
"noEventsInList": {
"message": "There are no events to list."
"message": "لا توجد أية أحداث لعرضها."
},
"newOrganization": {
"message": "مؤسسة جديدة"
@@ -721,7 +721,7 @@
"message": "أنت لا تنتمي إلى أي مؤسسة. تسمح لك المؤسسات بمشاركة العناصر بأمان مع مستخدمين آخرين."
},
"notificationSentDevice": {
"message": "A notification has been sent to your device."
"message": "تم إرسال إشعار إلى جهازك."
},
"versionNumber": {
"message": "الإصدار $VERSION_NUMBER$",
@@ -757,7 +757,7 @@
"message": "تذكرني"
},
"sendVerificationCodeEmailAgain": {
"message": "Send verification code email again"
"message": "إرسال رمز التحقق إلى البريد الإلكتروني مرة أخرى"
},
"useAnotherTwoStepMethod": {
"message": "Use another two-step login method"
@@ -2444,7 +2444,7 @@
"message": "Owner"
},
"ownerDesc": {
"message": "The highest access user that can manage all aspects of your organization."
"message": "Manage all aspects of your organization, including billing and subscriptions"
},
"clientOwnerDesc": {
"message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization."
@@ -2453,19 +2453,19 @@
"message": "Admin"
},
"adminDesc": {
"message": "Admins can access and manage all items, collections and users in your organization."
"message": "Manage organization access, all collections, members, reporting, and security settings"
},
"user": {
"message": "User"
},
"userDesc": {
"message": "A regular user with access to assigned collections in your organization."
"message": "Access and add items to assigned collections"
},
"manager": {
"message": "Manager"
},
"managerDesc": {
"message": "Managers can access and manage assigned collections in your organization."
"message": "Create, delete, and manage access in assigned collections"
},
"all": {
"message": "All"
@@ -2567,7 +2567,7 @@
}
},
"viewAllLoginOptions": {
"message": "View all log in options"
"message": "عرض جميع خيارات تسجيل الدخول"
},
"viewedItemId": {
"message": "Viewed item $ID$.",
@@ -2876,7 +2876,7 @@
"message": "Invalid date range."
},
"errorOccurred": {
"message": "An error has occurred."
"message": "لقد حدث خطأ."
},
"userAccess": {
"message": "User access"
@@ -2954,7 +2954,7 @@
"message": "Unable to verify your email. Try sending a new verification email."
},
"emailVerificationRequired": {
"message": "Email verification required"
"message": "تأكيد البريد الإلكتروني مطلوب"
},
"emailVerificationRequiredDesc": {
"message": "You must verify your email to use this feature."
@@ -2990,7 +2990,7 @@
}
},
"rememberEmail": {
"message": "Remember email"
"message": "تذكرني"
},
"recoverAccountTwoStepDesc": {
"message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account."
@@ -3426,10 +3426,10 @@
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
},
"fingerprintMatchInfo": {
"message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device."
"message": "الرجاء التأكد من أن الخزنة الخاصة بك غير مقفلة وأن بصمة الإصبع تتطابق مع الجهاز الآخر."
},
"fingerprintPhraseHeader": {
"message": "Fingerprint phrase"
"message": "بصمة الإصبع"
},
"dontAskFingerprintAgain": {
"message": "Never prompt to verify fingerprint phrases for invited users (not recommended)",
@@ -3692,10 +3692,10 @@
"message": "Organization identifier"
},
"ssoLogInWithOrgIdentifier": {
"message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin."
"message": "قم بتسجيل الدخول بسرعة باستخدام بوابة تسجيل الدخول الأحادي لمؤسستك. الرجاء إدخال معرف الـSSO الخاص بمؤسستك للبدء."
},
"enterpriseSingleSignOn": {
"message": "Enterprise single sign-on"
"message": "تسجيل الدخول الأُحادي للمؤسسات SSO"
},
"ssoHandOff": {
"message": "You may now close this tab and continue in the extension."
@@ -3713,10 +3713,10 @@
"message": "SSO validation failed"
},
"ssoIdentifierRequired": {
"message": "Organization SSO identifier is required."
"message": "معرف الSSO للمنظمة مطلوب"
},
"ssoIdentifier": {
"message": "SSO identifier"
"message": "معرف SSO"
},
"ssoIdentifierHint": {
"message": "Provide this ID to your members to login with SSO."
@@ -4117,7 +4117,22 @@
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
"message": "Grant customized permissions to members"
},
"customDescNonEnterpriseStart": {
"message": "Custom roles is an ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "enterprise feature",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Contact our support team to upgrade your subscription",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "To enable custom permissions the organization must be on an Enterprise 2020 plan."
},
"permissions": {
"message": "Permissions"
@@ -4285,10 +4300,10 @@
"message": "The deletion date provided is not valid."
},
"expirationDateAndTimeRequired": {
"message": "An expiration date and time are required."
"message": "مطلوب تاريخ ووقت انتهاء الصلاحية."
},
"deletionDateAndTimeRequired": {
"message": "A deletion date and time are required."
"message": "مطلوب تاريخ ووقت الحذف."
},
"dateParsingError": {
"message": "There was an error saving your deletion and expiration dates."
@@ -4441,7 +4456,7 @@
"message": "Resend invitations"
},
"resendNotification": {
"message": "Resend notification"
"message": "إعادة إرسال الإشعار"
},
"noSelectedUsersApplicable": {
"message": "This action is not applicable to any of the selected users."
@@ -5140,7 +5155,7 @@
"message": "1 field above needs your attention."
},
"fieldRequiredError": {
"message": "$FIELDNAME$ is required.",
"message": "$FIELDNAME$ مطلوب.",
"placeholders": {
"fieldname": {
"content": "$1",
@@ -5404,10 +5419,10 @@
"description": "the text, 'SCIM', is an acronymn and should not be translated."
},
"inputRequired": {
"message": "Input is required."
"message": "هذا الحقل مطلوب."
},
"inputEmail": {
"message": "Input is not an email address."
"message": "القيمة المدخلة ليست عنوان بريد إلكتروني."
},
"inputMinLength": {
"message": "Input must be at least $COUNT$ characters long.",
@@ -5455,10 +5470,10 @@
"message": "Number of users"
},
"loggingInAs": {
"message": "Logging in as"
"message": "تسجيل الدخول كـ"
},
"notYou": {
"message": "Not you?"
"message": "ليس حسابك؟"
},
"multiSelectPlaceholder": {
"message": "-- Type to Filter --"

View File

@@ -4119,6 +4119,21 @@
"customDesc": {
"message": "Qabaqcıl konfiqurasiya üçün istifadəçi icazələrinin daha təfsilatlı nəzarətinə icazə verər."
},
"customDescNonEnterpriseStart": {
"message": "Özəl rollar ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "bir müəssisə özəlliyidir",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Abunəliyinizi yüksəltmək üçün dəstək komandamızla əlaqə saxlayın",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "Özəl icazələri fəallaşdırmaq üçün təşkilat, Enterprise 2020 planında olmalıdır."
},
"permissions": {
"message": "İcazələr"
},

View File

@@ -4119,6 +4119,21 @@
"customDesc": {
"message": "Дазваляе больш паглыблена кантраляваць дазволы карыстальніка для дадатковых канфігурацый."
},
"customDescNonEnterpriseStart": {
"message": "Карыстальніцкія ролі з'яўляюцца ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "функцыяй для прадпрыемства",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Звяжыцеся з нашай камандай падтрымкі для паляпшэння падпіскі",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "Каб уключыць дазволы карыстальніка, арганізацыя павінна мець тарыфны план Enterprise 2020."
},
"permissions": {
"message": "Дазволы"
},

View File

@@ -582,7 +582,7 @@
"message": "Създаване на абонамент"
},
"newAroundHere": {
"message": "New around here?"
"message": "За пръв път ли сте тук?"
},
"startTrial": {
"message": "Стартиране на пробния период"
@@ -4119,6 +4119,21 @@
"customDesc": {
"message": "По-специфичен контрол на правата на потребителите при по-сложни варианти."
},
"customDescNonEnterpriseStart": {
"message": "Фукционалността „Персонализирани роли“ е ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "за големи организации",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Свържете се с нашия екип по поддръжката, за да надградите абонамента си.",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "To enable custom permissions the organization must be on an Enterprise 2020 plan."
},
"permissions": {
"message": "Права"
},

View File

@@ -2444,7 +2444,7 @@
"message": "Owner"
},
"ownerDesc": {
"message": "The highest access user that can manage all aspects of your organization."
"message": "Manage all aspects of your organization, including billing and subscriptions"
},
"clientOwnerDesc": {
"message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization."
@@ -2453,19 +2453,19 @@
"message": "Admin"
},
"adminDesc": {
"message": "Admins can access and manage all items, collections and users in your organization."
"message": "Manage organization access, all collections, members, reporting, and security settings"
},
"user": {
"message": "User"
},
"userDesc": {
"message": "A regular user with access to assigned collections in your organization."
"message": "Access and add items to assigned collections"
},
"manager": {
"message": "Manager"
},
"managerDesc": {
"message": "Managers can access and manage assigned collections in your organization."
"message": "Create, delete, and manage access in assigned collections"
},
"all": {
"message": "All"
@@ -4117,7 +4117,22 @@
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
"message": "Grant customized permissions to members"
},
"customDescNonEnterpriseStart": {
"message": "Custom roles is an ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "enterprise feature",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Contact our support team to upgrade your subscription",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "To enable custom permissions the organization must be on an Enterprise 2020 plan."
},
"permissions": {
"message": "Permissions"

View File

@@ -2444,7 +2444,7 @@
"message": "Owner"
},
"ownerDesc": {
"message": "The highest access user that can manage all aspects of your organization."
"message": "Manage all aspects of your organization, including billing and subscriptions"
},
"clientOwnerDesc": {
"message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization."
@@ -2453,19 +2453,19 @@
"message": "Admin"
},
"adminDesc": {
"message": "Admins can access and manage all items, collections and users in your organization."
"message": "Manage organization access, all collections, members, reporting, and security settings"
},
"user": {
"message": "User"
},
"userDesc": {
"message": "A regular user with access to assigned collections in your organization."
"message": "Access and add items to assigned collections"
},
"manager": {
"message": "Manager"
},
"managerDesc": {
"message": "Managers can access and manage assigned collections in your organization."
"message": "Create, delete, and manage access in assigned collections"
},
"all": {
"message": "All"
@@ -4117,7 +4117,22 @@
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
"message": "Grant customized permissions to members"
},
"customDescNonEnterpriseStart": {
"message": "Custom roles is an ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "enterprise feature",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Contact our support team to upgrade your subscription",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "To enable custom permissions the organization must be on an Enterprise 2020 plan."
},
"permissions": {
"message": "Permissions"

View File

@@ -2444,7 +2444,7 @@
"message": "Propietari"
},
"ownerDesc": {
"message": "L'usuari d'accés més elevat que pot gestionar tots els aspectes de la vostra organització."
"message": "Gestioneu tots els aspectes de la vostra organització, incloses la facturació i les subscripcions"
},
"clientOwnerDesc": {
"message": "Aquest usuari ha de ser independent del proveïdor. Si el proveïdor està desvinculat de l'organització, aquest usuari mantindrà la propietat de l'organització."
@@ -2453,19 +2453,19 @@
"message": "Administrador"
},
"adminDesc": {
"message": "Els administradors poden accedir i gestionar tots els elements, col·leccions i usuaris de la vostra organització."
"message": "Gestioneu l'accés a l'organització, totes les col·leccions, els membres, els informes i la configuració de seguretat"
},
"user": {
"message": "Usuari"
},
"userDesc": {
"message": "Un usuari habitual amb accés a les col·leccions assignades a la vostra organització."
"message": "Accediu i afegiu elements a les col·leccions assignades"
},
"manager": {
"message": "Gestor"
},
"managerDesc": {
"message": "Els gestors poden accedir i gestionar les col·leccions assignades a la vostra organització."
"message": "Creeu, suprimiu i gestioneu l'accés a les col·leccions assignades"
},
"all": {
"message": "Tot"
@@ -4117,7 +4117,22 @@
"message": "Personalitzat"
},
"customDesc": {
"message": "Permet un control més granular dels permisos d'usuari per a configuracions avançades."
"message": "Concediu permisos personalitzats als membres"
},
"customDescNonEnterpriseStart": {
"message": "Els rols personalitzats són una ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "característica empresarial",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Contacta amb el nostre equip d'assistència per actualitzar la teua subscripció",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "Per habilitar els permisos personalitzats, l'organització ha de tenir un pla Enterprise 2020."
},
"permissions": {
"message": "Permisos"
@@ -4420,7 +4435,7 @@
"message": "La resposta de les claus dorganització és nul·la"
},
"resetPasswordDetailsError": {
"message": "La resposta Restableix detalls de contrasenya és nul·la"
"message": "La resposta dels detalls de restabliment de la contrasenya és nul·la"
},
"trashCleanupWarning": {
"message": "Els elements que porten més de 30 dies a la paperera se suprimiran automàticament."
@@ -4516,7 +4531,7 @@
"message": "Configuració del proveïdor"
},
"setupProviderLoginDesc": {
"message": "T'han convidat a configurar un proveïdor nou. Per continuar, heu d'iniciar sessió o crear un nou compte de Bitwarden."
"message": "Us han convidat a configurar un proveïdor nou. Per continuar, heu d'iniciar sessió o crear un nou compte de Bitwarden."
},
"setupProviderDesc": {
"message": "Introduïu els detalls a continuació per completar la configuració del proveïdor. Poseu-vos en contacte amb el servei d'atenció al client si teniu cap pregunta."
@@ -4547,7 +4562,7 @@
"message": "Els usuaris del servei poden accedir i gestionar totes les organitzacions client."
},
"providerInviteUserDesc": {
"message": "Convida un nou usuari a la vostra organització introduint l'adreça electrònica del compte de Bitwarden a continuació. Si encara no tenen un compte de Bitwarden, se us demanarà que creeu un compte nou."
"message": "Convideu un nou usuari a la vostra organització introduint l'adreça electrònica del compte de Bitwarden a continuació. Si encara no tenen un compte de Bitwarden, se us demanarà que creeu un compte nou."
},
"joinProvider": {
"message": "Uniu-vos al proveïdor"
@@ -4556,7 +4571,7 @@
"message": "Heu estat convidat a unir-vos al proveïdor llistat més amunt. Per acceptar la invitació, heu d'iniciar sessió o crear un compte nou a Bitwarden."
},
"providerInviteAcceptFailed": {
"message": "No es pot acceptar la invitació. Demaneu a l'administrador d'una organització que envie una invitació nova."
"message": "No es pot acceptar la invitació. Demaneu a un administrador del proveïdor que envie una nova invitació."
},
"providerInviteAcceptedDesc": {
"message": "Podeu accedir a aquesta organització una vegada que un administrador confirme la vostra pertinença. Rebreu un correu electrònic quan això passe."
@@ -4608,10 +4623,10 @@
"message": "S'ha inhabilitat el proveïdor."
},
"providerUpdated": {
"message": "S'ha actualitzat el proveïdor"
"message": "S'ha guardat el proveïdor"
},
"yourProviderIs": {
"message": "El vostre proveïdor és $PROVIDER$. Tenen privilegis administratius i de facturació en la vostra organització.",
"message": "El vostre proveïdor és $PROVIDER$. Té privilegis administratius i de facturació en la vostra organització.",
"placeholders": {
"provider": {
"content": "$1",
@@ -4635,7 +4650,7 @@
"message": "Afig"
},
"updatedMasterPassword": {
"message": "Contrasenya mestra actualitzada"
"message": "Contrasenya mestra guardada"
},
"updateMasterPassword": {
"message": "Actualitza contrasenya mestra"

View File

@@ -936,10 +936,10 @@
"message": "Set a password to encrypt the export and import it to any Bitwarden account using the password for decryption."
},
"fileTypeHeading": {
"message": "File type"
"message": "Typ souboru"
},
"accountBackup": {
"message": "Account backup"
"message": "Zálohování účtu"
},
"passwordProtected": {
"message": "Password protected"
@@ -1199,7 +1199,7 @@
"message": "Choose File"
},
"noFileChosen": {
"message": "No file chosen"
"message": "Není vybrán žádný soubor"
},
"orCopyPasteFileContents": {
"message": "nebo zkopírujte a vložte obsah souboru"
@@ -2453,13 +2453,13 @@
"message": "Administrátor"
},
"adminDesc": {
"message": "Administrátoři mohou prohlížet a spravovat všechny položky, sbírky a uživatele ve vaší organizaci."
"message": "Administrátoři mohou prohlížet a spravovat všechny položky, kolekce a uživatele ve vaší organizaci."
},
"user": {
"message": "Uživatel"
},
"userDesc": {
"message": "Běžný uživatel s přístupem k přiřazeným kolekcím vaší organizace."
"message": "A regular user with access to your organization's collections."
},
"manager": {
"message": "Správce"
@@ -2897,7 +2897,7 @@
"message": "Znovu poslat pozvánku"
},
"resendEmail": {
"message": "Resend email"
"message": "Znovu poslat e-mail"
},
"hasBeenReinvited": {
"message": "Uživatel $USER$ byl znovu pozván.",
@@ -3345,7 +3345,7 @@
"description": "ex. Date this item was updated"
},
"dateCreated": {
"message": "Created",
"message": "Vytvořeno",
"description": "ex. Date this item was created"
},
"datePasswordUpdated": {
@@ -3418,7 +3418,7 @@
"message": "Ve vašem trezoru jsou staré přílohy vyžadující opravu před změnou šifrovacího klíče k vašemu účtu."
},
"yourAccountsFingerprint": {
"message": "Fráze otisku prstu vašeho účtu",
"message": "Fráze otisku vašeho účtu",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
},
"fingerprintEnsureIntegrityVerify": {
@@ -3432,7 +3432,7 @@
"message": "Fingerprint phrase"
},
"dontAskFingerprintAgain": {
"message": "Již se neptat na ověření fráze otisku prstu",
"message": "Již se neptat na ověření fráze otisku účtu (nedoporučeno)",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
},
"free": {
@@ -4119,14 +4119,29 @@
"customDesc": {
"message": "Umožňuje větší kontrolu nad uživatelských oprávnění pro pokročilé konfigurace."
},
"customDescNonEnterpriseStart": {
"message": "Custom roles is an ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "enterprise feature",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Contact our support team to upgrade your subscription",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "To enable custom permissions the organization must be on an Enterprise 2020 plan."
},
"permissions": {
"message": "Oprávnění"
},
"permission": {
"message": "Permission"
"message": "Oprávnění"
},
"managerPermissions": {
"message": "Manager Permissions"
"message": "Spravovat oprávnění"
},
"adminPermissions": {
"message": "Admin Permissions"
@@ -5216,7 +5231,7 @@
"description": "This is used by screen readers to indicate the organization that is currently being shown to the user."
},
"accountSettings": {
"message": "Account settings"
"message": "Nastavení účtu"
},
"generator": {
"message": "Generator"
@@ -5443,7 +5458,7 @@
"message": "Zapnuto"
},
"members": {
"message": "Members"
"message": "Členové"
},
"reporting": {
"message": "Reporting"
@@ -5479,7 +5494,7 @@
"message": "To"
},
"member": {
"message": "Member"
"message": "Člen"
},
"update": {
"message": "Update"

File diff suppressed because it is too large Load Diff

View File

@@ -4119,6 +4119,21 @@
"customDesc": {
"message": "Feinere Kontrolle der Benutzer Berechtigungen für erweiterte Konfigurationen erlauben."
},
"customDescNonEnterpriseStart": {
"message": "Benutzerdefinierte Rollen sind eine ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "Funktion für Unternehmen",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Kontaktiere unser Kundenserviceteam, um dein Abo hochzustufen",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "Um benutzerdefinierte Berechtigungen zu ermöglichen, muss sich die Organisation auf einem 2020er Unternehmensabo befinden."
},
"permissions": {
"message": "Berechtigungen"
},

View File

@@ -4119,6 +4119,21 @@
"customDesc": {
"message": "Επιτρέπει πιο κοκκώδη έλεγχο, των δικαιωμάτων χρήστη για προηγμένες ρυθμίσεις."
},
"customDescNonEnterpriseStart": {
"message": "Custom roles is an ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "enterprise feature",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Contact our support team to upgrade your subscription",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "To enable custom permissions the organization must be on an Enterprise 2020 plan."
},
"permissions": {
"message": "Άδειες"
},

View File

@@ -2480,7 +2480,7 @@
"message": "Owner"
},
"ownerDesc": {
"message": "The highest access user that can manage all aspects of your organization."
"message": "Manage all aspects of your organization, including billing and subscriptions"
},
"clientOwnerDesc": {
"message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization."
@@ -2489,19 +2489,19 @@
"message": "Admin"
},
"adminDesc": {
"message": "Admins can access and manage all items, collections and users in your organization."
"message": "Manage organization access, all collections, members, reporting, and security settings"
},
"user": {
"message": "User"
},
"userDesc": {
"message": "A regular user with access to assigned collections in your organization."
"message": "Access and add items to assigned collections"
},
"manager": {
"message": "Manager"
},
"managerDesc": {
"message": "Managers can access and manage assigned collections in your organization."
"message": "Create, delete, and manage access in assigned collections"
},
"all": {
"message": "All"
@@ -4162,7 +4162,22 @@
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
"message": "Grant customized permissions to members"
},
"customDescNonEnterpriseStart": {
"message": "Custom roles is an ",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseLink": {
"message": "enterprise feature",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customDescNonEnterpriseEnd": {
"message": ". Contact our support team to upgrade your subscription",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'"
},
"customNonEnterpriseError": {
"message": "To enable custom permissions the organization must be on an Enterprise 2020 plan."
},
"permissions": {
"message": "Permissions"
@@ -5526,6 +5541,258 @@
"multiSelectClearAll": {
"message": "Clear all"
},
"projects":{
"message": "Projects"
},
"lastEdited":{
"message": "Last Edited"
},
"editSecret":{
"message": "Edit Secret"
},
"addSecret":{
"message": "Add Secret"
},
"copySecretName":{
"message": "Copy Secret Name"
},
"copySecretValue":{
"message": "Copy Secret Value"
},
"deleteSecret":{
"message": "Delete Secret"
},
"deleteSecrets":{
"message": "Delete Secrets"
},
"project":{
"message": "Project"
},
"editProject":{
"message": "Edit Project"
},
"viewProject":{
"message": "View Project"
},
"deleteProject":{
"message": "Delete Project"
},
"deleteProjects":{
"message": "Delete Projects"
},
"secret":{
"message": "Secret"
},
"serviceAccount":{
"message": "Service Account"
},
"serviceAccounts":{
"message": "Service Accounts"
},
"new":{
"message": "New"
},
"secrets":{
"message":"Secrets"
},
"nameValuePair":{
"message":"Name/Value Pair"
},
"secretEdited":{
"message":"Secret edited"
},
"secretCreated":{
"message":"Secret created"
},
"newSecret":{
"message":"New Secret"
},
"newServiceAccount":{
"message":"New Service Account"
},
"importSecrets":{
"message":"Import Secrets"
},
"secretsNoItemsTitle":{
"message":"No secrets to show"
},
"secretsNoItemsMessage":{
"message": "To get started, add a new secret or import secrets."
},
"serviceAccountsNoItemsTitle":{
"message":"Nothing to show yet"
},
"serviceAccountsNoItemsMessage":{
"message": "Create a new Service Account to get started automating secret access."
},
"searchSecrets":{
"message":"Search Secrets"
},
"deleteServiceAccounts":{
"message":"Delete Service Accounts"
},
"deleteServiceAccount":{
"message":"Delete Service Account"
},
"viewServiceAccount":{
"message":"View Service Account"
},
"searchServiceAccounts":{
"message":"Search Service Accounts"
},
"addProject":{
"message": "Add Project"
},
"projectEdited":{
"message":"Project edited"
},
"projectSaved":{
"message":"Project saved"
},
"projectCreated":{
"message":"Project created"
},
"projectName":{
"message":"Project Name"
},
"newProject":{
"message":"New Project"
},
"softDeleteSecretWarning":{
"message":"Deleting secrets can affect existing integrations."
},
"softDeletesSuccessToast":{
"message":"Secrets sent to trash"
},
"serviceAccountCreated":{
"message":"Service Account Created"
},
"smAccess":{
"message":"Access"
},
"projectCommaSecret":{
"message":"Project, Secret"
},
"serviceAccountName":{
"message": "Service account name"
},
"newSaSelectAccess":{
"message": "Type or Select Projects or Secrets"
},
"newSaTypeToFilter":{
"message": "Type to Filter"
},
"deleteProjectsToast":{
"message": "Projects deleted"
},
"deleteProjectToast":{
"message": "The project and all associated secrets have been deleted"
},
"deleteProjectDialogMessage": {
"message": "Deleting project $PROJECT$ is permanent and irreversible.",
"placeholders": {
"project": {
"content": "$1",
"example": "project name"
}
}
},
"deleteProjectInputLabel": {
"message": "Type \"$CONFIRM$\" to continue",
"placeholders": {
"confirm": {
"content": "$1",
"example": "Delete 3 Projects"
}
}
},
"deleteProjectConfirmMessage":{
"message": "Delete $PROJECT$",
"placeholders": {
"project": {
"content": "$1",
"example": "project name"
}
}
},
"deleteProjectsConfirmMessage":{
"message": "Delete $COUNT$ Projects",
"placeholders": {
"count": {
"content": "$1",
"example": "2"
}
}
},
"deleteProjectsDialogMessage":{
"message": "Deleting projects is permanent and irreversible."
},
"projectsNoItemsTitle":{
"message": "No projects to display"
},
"projectsNoItemsMessage":{
"message": "Add a new project to get started organizing secrets."
},
"smConfirmationRequired":{
"message": "Confirmation required"
},
"bulkDeleteProjectsErrorMessage":{
"message": "The following projects could not be deleted:"
},
"softDeleteSuccessToast":{
"message":"Secret sent to trash"
},
"searchProjects":{
"message":"Search Projects"
},
"accessTokens": {
"message": "Access tokens"
},
"createAccessToken": {
"message": "Create access token"
},
"expires": {
"message": "Expires"
},
"canRead": {
"message": "Can Read"
},
"accessTokensNoItemsTitle": {
"message": "No access tokens to show"
},
"accessTokensNoItemsDesc": {
"message": "To get started, create an access token"
},
"downloadAccessToken": {
"message": "Download or copy before closing."
},
"expiresOnAccessToken": {
"message": "Expires on:"
},
"accessTokenCallOutTitle": {
"message": "Access tokens are not stored and cannot be retrieved"
},
"copyToken": {
"message": "Copy token"
},
"accessToken": {
"message": "Access token"
},
"accessTokenExpirationRequired": {
"message": "Expiration date required"
},
"accessTokenCreatedAndCopied": {
"message": "Access token created and copied to clipboard"
},
"accessTokenPermissionsBetaNotification": {
"message": "Permissions management is unavailable for beta."
},
"revokeAccessToken": {
"message": "Revoke Access Token"
},
"submenu": {
"message": "Submenu"
},
"from": {
"message": "From"
},

Some files were not shown because too many files have changed in this diff Show More