mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 17:53:39 +00:00
Merge branch 'main' into auth/pm-8111/browser-refresh-login-component
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@bitwarden/web-vault",
|
||||
"version": "2024.10.1",
|
||||
"version": "2024.10.2",
|
||||
"scripts": {
|
||||
"build:oss": "webpack",
|
||||
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
*ngIf="organization.canAccessReports"
|
||||
></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
<bit-nav-item
|
||||
*ngIf="isAccessIntelligenceFeatureEnabled"
|
||||
[text]="'accessIntelligence' | i18n"
|
||||
route="access-intelligence"
|
||||
></bit-nav-item>
|
||||
<bit-nav-group
|
||||
icon="bwi-billing"
|
||||
[text]="'billing' | i18n"
|
||||
|
||||
@@ -51,6 +51,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
showPaymentAndHistory$: Observable<boolean>;
|
||||
hideNewOrgButton$: Observable<boolean>;
|
||||
organizationIsUnmanaged$: Observable<boolean>;
|
||||
isAccessIntelligenceFeatureEnabled = false;
|
||||
|
||||
private _destroy = new Subject<void>();
|
||||
|
||||
@@ -70,6 +71,10 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
async ngOnInit() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
|
||||
this.isAccessIntelligenceFeatureEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.AccessIntelligence,
|
||||
);
|
||||
|
||||
this.organization$ = this.route.params
|
||||
.pipe(takeUntil(this._destroy))
|
||||
.pipe<string>(map((p) => p.organizationId))
|
||||
|
||||
@@ -1,100 +1,67 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="resetPasswordTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title" id="resetPasswordTitle">
|
||||
{{ "recoverAccount" | i18n }}
|
||||
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
||||
</h1>
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog [title]="'recoverAccount' | i18n" [subtitle]="data.name">
|
||||
<ng-container bitDialogContent>
|
||||
<bit-callout type="warning"
|
||||
>{{ "resetPasswordLoggedOutWarning" | i18n: loggedOutWarningName }}
|
||||
</bit-callout>
|
||||
<auth-password-callout
|
||||
[policy]="enforcedPolicyOptions"
|
||||
message="resetPasswordMasterPasswordPolicyInEffect"
|
||||
*ngIf="enforcedPolicyOptions"
|
||||
>
|
||||
</auth-password-callout>
|
||||
<bit-form-field>
|
||||
<bit-label>
|
||||
{{ "newPassword" | i18n }}
|
||||
</bit-label>
|
||||
<input
|
||||
id="newPassword"
|
||||
bitInput
|
||||
[type]="showPassword ? 'text' : 'password'"
|
||||
name="NewPassword"
|
||||
formControlName="newPassword"
|
||||
required
|
||||
appInputVerbatim
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<app-callout type="warning"
|
||||
>{{ "resetPasswordLoggedOutWarning" | i18n: loggedOutWarningName }}
|
||||
</app-callout>
|
||||
<auth-password-callout
|
||||
[policy]="enforcedPolicyOptions"
|
||||
message="resetPasswordMasterPasswordPolicyInEffect"
|
||||
*ngIf="enforcedPolicyOptions"
|
||||
>
|
||||
</auth-password-callout>
|
||||
<div class="row">
|
||||
<div class="col form-group">
|
||||
<div class="d-flex">
|
||||
<label for="newPassword">{{ "newPassword" | i18n }}</label>
|
||||
<div class="ml-auto d-flex">
|
||||
<a
|
||||
href="#"
|
||||
class="d-block mr-2 bwi-icon-above-input"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'generatePassword' | i18n }}"
|
||||
(click)="generatePassword()"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-fw bwi-refresh" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group mb-1">
|
||||
<input
|
||||
id="newPassword"
|
||||
class="form-control text-monospace"
|
||||
appAutofocus
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="NewPassword"
|
||||
[(ngModel)]="newPassword"
|
||||
required
|
||||
appInputVerbatim
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'copyPassword' | i18n }}"
|
||||
(click)="copy(newPassword)"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-password-strength
|
||||
[password]="newPassword"
|
||||
[email]="email"
|
||||
[showText]="true"
|
||||
(passwordStrengthResult)="getStrengthResult($event)"
|
||||
>
|
||||
</app-password-strength>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
bitIconButton="bwi-generate"
|
||||
bitSuffix
|
||||
[appA11yTitle]="'generatePassword' | i18n"
|
||||
(click)="generatePassword()"
|
||||
></button>
|
||||
<button
|
||||
type="button"
|
||||
bitSuffix
|
||||
[bitIconButton]="showPassword ? 'bwi-eye-slash' : 'bwi-eye'"
|
||||
buttonType="secondary"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword()"
|
||||
></button>
|
||||
<button
|
||||
type="button"
|
||||
bitSuffix
|
||||
bitIconButton="bwi-clone"
|
||||
appA11yTitle="{{ 'copyPassword' | i18n }}"
|
||||
(click)="copy()"
|
||||
></button>
|
||||
</bit-form-field>
|
||||
<tools-password-strength
|
||||
[password]="formGroup.value.newPassword"
|
||||
[email]="data.email"
|
||||
[showText]="true"
|
||||
(passwordStrengthScore)="getStrengthScore($event)"
|
||||
>
|
||||
</tools-password-strength>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary" bitFormButton type="submit">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button bitButton buttonType="secondary" bitDialogClose type="button">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from "@angular/core";
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
import zxcvbn from "zxcvbn";
|
||||
|
||||
import { PasswordStrengthComponent } from "@bitwarden/angular/tools/password-strength/password-strength.component";
|
||||
import { PasswordStrengthV2Component } from "@bitwarden/angular/tools/password-strength/password-strength-v2.component";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -22,27 +15,60 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legac
|
||||
|
||||
import { OrganizationUserResetPasswordService } from "../services/organization-user-reset-password/organization-user-reset-password.service";
|
||||
|
||||
/**
|
||||
* Encapsulates a few key data inputs needed to initiate an account recovery
|
||||
* process for the organization user in question.
|
||||
*/
|
||||
export type ResetPasswordDialogData = {
|
||||
/**
|
||||
* The organization user's full name
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The organization user's email address
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* The `organizationUserId` for the user
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The organization's `organizationId`
|
||||
*/
|
||||
organizationId: string;
|
||||
};
|
||||
|
||||
export enum ResetPasswordDialogResult {
|
||||
Ok = "ok",
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "app-reset-password",
|
||||
templateUrl: "reset-password.component.html",
|
||||
})
|
||||
/**
|
||||
* Used in a dialog for initiating the account recovery process against a
|
||||
* given organization user. An admin will access this form when they want to
|
||||
* reset a user's password and log them out of sessions.
|
||||
*/
|
||||
export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
@Input() name: string;
|
||||
@Input() email: string;
|
||||
@Input() id: string;
|
||||
@Input() organizationId: string;
|
||||
@Output() passwordReset = new EventEmitter();
|
||||
@ViewChild(PasswordStrengthComponent) passwordStrengthComponent: PasswordStrengthComponent;
|
||||
formGroup = this.formBuilder.group({
|
||||
newPassword: ["", Validators.required],
|
||||
});
|
||||
|
||||
@ViewChild(PasswordStrengthV2Component) passwordStrengthComponent: PasswordStrengthV2Component;
|
||||
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
newPassword: string = null;
|
||||
showPassword = false;
|
||||
passwordStrengthResult: zxcvbn.ZXCVBNResult;
|
||||
formPromise: Promise<any>;
|
||||
passwordStrengthScore: number;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: ResetPasswordDialogData,
|
||||
private resetPasswordService: OrganizationUserResetPasswordService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
@@ -51,6 +77,8 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
private logService: LogService,
|
||||
private dialogService: DialogService,
|
||||
private toastService: ToastService,
|
||||
private formBuilder: FormBuilder,
|
||||
private dialogRef: DialogRef<ResetPasswordDialogResult>,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -69,13 +97,15 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get loggedOutWarningName() {
|
||||
return this.name != null ? this.name : this.i18nService.t("thisUser");
|
||||
return this.data.name != null ? this.data.name : this.i18nService.t("thisUser");
|
||||
}
|
||||
|
||||
async generatePassword() {
|
||||
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
|
||||
this.newPassword = await this.passwordGenerationService.generatePassword(options);
|
||||
this.passwordStrengthComponent.updatePasswordStrength(this.newPassword);
|
||||
this.formGroup.patchValue({
|
||||
newPassword: await this.passwordGenerationService.generatePassword(options),
|
||||
});
|
||||
this.passwordStrengthComponent.updatePasswordStrength(this.formGroup.value.newPassword);
|
||||
}
|
||||
|
||||
togglePassword() {
|
||||
@@ -83,7 +113,8 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
document.getElementById("newPassword").focus();
|
||||
}
|
||||
|
||||
copy(value: string) {
|
||||
copy() {
|
||||
const value = this.formGroup.value.newPassword;
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
@@ -96,9 +127,9 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
submit = async () => {
|
||||
// Validation
|
||||
if (this.newPassword == null || this.newPassword === "") {
|
||||
if (this.formGroup.value.newPassword == null || this.formGroup.value.newPassword === "") {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
@@ -107,7 +138,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.newPassword.length < Utils.minimumPasswordLength) {
|
||||
if (this.formGroup.value.newPassword.length < Utils.minimumPasswordLength) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
@@ -119,8 +150,8 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
if (
|
||||
this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
this.passwordStrengthResult.score,
|
||||
this.newPassword,
|
||||
this.passwordStrengthScore,
|
||||
this.formGroup.value.newPassword,
|
||||
this.enforcedPolicyOptions,
|
||||
)
|
||||
) {
|
||||
@@ -132,7 +163,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.passwordStrengthResult.score < 3) {
|
||||
if (this.passwordStrengthScore < 3) {
|
||||
const result = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "weakMasterPassword" },
|
||||
content: { key: "weakMasterPasswordDesc" },
|
||||
@@ -145,26 +176,29 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.resetPasswordService.resetMasterPassword(
|
||||
this.newPassword,
|
||||
this.email,
|
||||
this.id,
|
||||
this.organizationId,
|
||||
await this.resetPasswordService.resetMasterPassword(
|
||||
this.formGroup.value.newPassword,
|
||||
this.data.email,
|
||||
this.data.id,
|
||||
this.data.organizationId,
|
||||
);
|
||||
await this.formPromise;
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("resetPasswordSuccess"),
|
||||
});
|
||||
this.passwordReset.emit();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
this.formPromise = null;
|
||||
|
||||
this.dialogRef.close(ResetPasswordDialogResult.Ok);
|
||||
};
|
||||
|
||||
getStrengthScore(result: number) {
|
||||
this.passwordStrengthScore = result;
|
||||
}
|
||||
|
||||
getStrengthResult(result: zxcvbn.ZXCVBNResult) {
|
||||
this.passwordStrengthResult = result;
|
||||
}
|
||||
static open = (dialogService: DialogService, input: DialogConfig<ResetPasswordDialogData>) => {
|
||||
return dialogService.open<ResetPasswordDialogResult>(ResetPasswordComponent, input);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,7 +70,10 @@ import {
|
||||
MemberDialogTab,
|
||||
openUserAddEditDialog,
|
||||
} from "./components/member-dialog";
|
||||
import { ResetPasswordComponent } from "./components/reset-password.component";
|
||||
import {
|
||||
ResetPasswordComponent,
|
||||
ResetPasswordDialogResult,
|
||||
} from "./components/reset-password.component";
|
||||
|
||||
class MembersTableDataSource extends PeopleTableDataSource<OrganizationUserView> {
|
||||
protected statusType = OrganizationUserStatusType;
|
||||
@@ -663,24 +666,19 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
}
|
||||
|
||||
async resetPassword(user: OrganizationUserView) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
ResetPasswordComponent,
|
||||
this.resetPasswordModalRef,
|
||||
(comp) => {
|
||||
comp.name = this.userNamePipe.transform(user);
|
||||
comp.email = user != null ? user.email : null;
|
||||
comp.organizationId = this.organization.id;
|
||||
comp.id = user != null ? user.id : null;
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
comp.passwordReset.subscribe(() => {
|
||||
modal.close();
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.load();
|
||||
});
|
||||
const dialogRef = ResetPasswordComponent.open(this.dialogService, {
|
||||
data: {
|
||||
name: this.userNamePipe.transform(user),
|
||||
email: user != null ? user.email : null,
|
||||
organizationId: this.organization.id,
|
||||
id: user != null ? user.id : null,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === ResetPasswordDialogResult.Ok) {
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
|
||||
protected async removeUserConfirmationDialog(user: OrganizationUserView) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PasswordStrengthV2Component } from "@bitwarden/angular/tools/password-strength/password-strength-v2.component";
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth/angular";
|
||||
|
||||
import { LooseComponentsModule } from "../../../shared";
|
||||
@@ -24,6 +25,7 @@ import { MembersComponent } from "./members.component";
|
||||
UserDialogModule,
|
||||
PasswordCalloutComponent,
|
||||
ScrollingModule,
|
||||
PasswordStrengthV2Component,
|
||||
],
|
||||
declarations: [
|
||||
BulkConfirmComponent,
|
||||
|
||||
@@ -62,6 +62,13 @@ const routes: Routes = [
|
||||
(m) => m.OrganizationReportingModule,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "access-intelligence",
|
||||
loadChildren: () =>
|
||||
import("../../tools/access-intelligence/access-intelligence.module").then(
|
||||
(m) => m.AccessIntelligenceModule,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "billing",
|
||||
loadChildren: () =>
|
||||
|
||||
@@ -12,11 +12,14 @@ import {
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction";
|
||||
import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response";
|
||||
import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { HttpStatusCode } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
@@ -107,13 +110,24 @@ export class SsoComponent extends BaseSsoComponent implements OnInit {
|
||||
// show loading spinner
|
||||
this.loggingIn = true;
|
||||
try {
|
||||
const response: OrganizationDomainSsoDetailsResponse =
|
||||
await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email);
|
||||
if (await this.configService.getFeatureFlag(FeatureFlag.VerifiedSsoDomainEndpoint)) {
|
||||
const response: ListResponse<VerifiedOrganizationDomainSsoDetailsResponse> =
|
||||
await this.orgDomainApiService.getVerifiedOrgDomainsByEmail(qParams.email);
|
||||
|
||||
if (response?.ssoAvailable && response?.verifiedDate) {
|
||||
this.identifierFormControl.setValue(response.organizationIdentifier);
|
||||
await this.submit();
|
||||
return;
|
||||
if (response.data.length > 0) {
|
||||
this.identifierFormControl.setValue(response.data[0].organizationIdentifier);
|
||||
await this.submit();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const response: OrganizationDomainSsoDetailsResponse =
|
||||
await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email);
|
||||
|
||||
if (response?.ssoAvailable && response?.verifiedDate) {
|
||||
this.identifierFormControl.setValue(response.organizationIdentifier);
|
||||
await this.submit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.handleGetClaimedDomainByEmailError(error);
|
||||
|
||||
@@ -11,8 +11,12 @@
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="invoices || transactions">
|
||||
<app-billing-history [invoices]="invoices" [transactions]="transactions"></app-billing-history>
|
||||
<ng-container *ngIf="openInvoices || paidInvoices || transactions">
|
||||
<app-billing-history
|
||||
[openInvoices]="openInvoices"
|
||||
[paidInvoices]="paidInvoices"
|
||||
[transactions]="transactions"
|
||||
></app-billing-history>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
|
||||
@@ -14,7 +14,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
export class BillingHistoryViewComponent implements OnInit {
|
||||
loading = false;
|
||||
firstLoaded = false;
|
||||
invoices: BillingInvoiceResponse[] = [];
|
||||
openInvoices: BillingInvoiceResponse[] = [];
|
||||
paidInvoices: BillingInvoiceResponse[] = [];
|
||||
transactions: BillingTransactionResponse[] = [];
|
||||
hasAdditionalHistory: boolean = false;
|
||||
|
||||
@@ -41,8 +42,14 @@ export class BillingHistoryViewComponent implements OnInit {
|
||||
}
|
||||
this.loading = true;
|
||||
|
||||
const invoicesPromise = this.accountBillingApiService.getBillingInvoices(
|
||||
this.invoices.length > 0 ? this.invoices[this.invoices.length - 1].id : null,
|
||||
const openInvoicesPromise = this.accountBillingApiService.getBillingInvoices(
|
||||
"open",
|
||||
this.openInvoices.length > 0 ? this.openInvoices[this.openInvoices.length - 1].id : null,
|
||||
);
|
||||
|
||||
const paidInvoicesPromise = this.accountBillingApiService.getBillingInvoices(
|
||||
"paid",
|
||||
this.paidInvoices.length > 0 ? this.paidInvoices[this.paidInvoices.length - 1].id : null,
|
||||
);
|
||||
|
||||
const transactionsPromise = this.accountBillingApiService.getBillingTransactions(
|
||||
@@ -51,15 +58,20 @@ export class BillingHistoryViewComponent implements OnInit {
|
||||
: null,
|
||||
);
|
||||
|
||||
const accountInvoices = await invoicesPromise;
|
||||
const accountTransactions = await transactionsPromise;
|
||||
const openInvoices = await openInvoicesPromise;
|
||||
const paidInvoices = await paidInvoicesPromise;
|
||||
const transactions = await transactionsPromise;
|
||||
|
||||
const pageSize = 5;
|
||||
|
||||
this.invoices = [...this.invoices, ...accountInvoices];
|
||||
this.transactions = [...this.transactions, ...accountTransactions];
|
||||
this.hasAdditionalHistory = !(
|
||||
accountInvoices.length < pageSize && accountTransactions.length < pageSize
|
||||
);
|
||||
this.openInvoices = [...this.openInvoices, ...openInvoices];
|
||||
this.paidInvoices = [...this.paidInvoices, ...paidInvoices];
|
||||
this.transactions = [...this.transactions, ...transactions];
|
||||
|
||||
this.hasAdditionalHistory =
|
||||
openInvoices.length >= pageSize ||
|
||||
paidInvoices.length >= pageSize ||
|
||||
transactions.length >= pageSize;
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,12 @@
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="invoices || transactions">
|
||||
<app-billing-history [invoices]="invoices" [transactions]="transactions"></app-billing-history>
|
||||
<ng-container *ngIf="openInvoices || paidInvoices || transactions">
|
||||
<app-billing-history
|
||||
[openInvoices]="openInvoices"
|
||||
[paidInvoices]="paidInvoices"
|
||||
[transactions]="transactions"
|
||||
></app-billing-history>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
||||
loading = false;
|
||||
firstLoaded = false;
|
||||
invoices: BillingInvoiceResponse[] = [];
|
||||
openInvoices: BillingInvoiceResponse[] = [];
|
||||
paidInvoices: BillingInvoiceResponse[] = [];
|
||||
transactions: BillingTransactionResponse[] = [];
|
||||
organizationId: string;
|
||||
hasAdditionalHistory: boolean = false;
|
||||
@@ -51,9 +52,16 @@ export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const invoicesPromise = this.organizationBillingApiService.getBillingInvoices(
|
||||
const openInvoicesPromise = this.organizationBillingApiService.getBillingInvoices(
|
||||
this.organizationId,
|
||||
this.invoices.length > 0 ? this.invoices[this.invoices.length - 1].id : null,
|
||||
"open",
|
||||
this.openInvoices.length > 0 ? this.openInvoices[this.openInvoices.length - 1].id : null,
|
||||
);
|
||||
|
||||
const paidInvoicesPromise = this.organizationBillingApiService.getBillingInvoices(
|
||||
this.organizationId,
|
||||
"paid",
|
||||
this.paidInvoices.length > 0 ? this.paidInvoices[this.paidInvoices.length - 1].id : null,
|
||||
);
|
||||
|
||||
const transactionsPromise = this.organizationBillingApiService.getBillingTransactions(
|
||||
@@ -63,13 +71,21 @@ export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
||||
: null,
|
||||
);
|
||||
|
||||
const invoices = await invoicesPromise;
|
||||
const openInvoices = await openInvoicesPromise;
|
||||
const paidInvoices = await paidInvoicesPromise;
|
||||
const transactions = await transactionsPromise;
|
||||
|
||||
const pageSize = 5;
|
||||
|
||||
this.invoices = [...this.invoices, ...invoices];
|
||||
this.openInvoices = [...this.openInvoices, ...openInvoices];
|
||||
this.paidInvoices = [...this.paidInvoices, ...paidInvoices];
|
||||
this.transactions = [...this.transactions, ...transactions];
|
||||
this.hasAdditionalHistory = !(invoices.length < pageSize && transactions.length < pageSize);
|
||||
|
||||
this.hasAdditionalHistory =
|
||||
openInvoices.length <= pageSize ||
|
||||
paidInvoices.length <= pageSize ||
|
||||
transactions.length <= pageSize;
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<bit-section>
|
||||
<h3 bitTypography="h3">{{ "invoices" | i18n }}</h3>
|
||||
<p bitTypography="body1" *ngIf="!invoices || !invoices.length">{{ "noInvoices" | i18n }}</p>
|
||||
<h3 bitTypography="h3">{{ "unpaid" | i18n }} {{ "invoices" | i18n }}</h3>
|
||||
<p bitTypography="body1" *ngIf="!openInvoices || !openInvoices.length">
|
||||
{{ "noUnpaidInvoices" | i18n }}
|
||||
</p>
|
||||
<bit-table>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let i of invoices">
|
||||
<tr bitRow *ngFor="let i of openInvoices">
|
||||
<td bitCell>{{ i.date | date: "mediumDate" }}</td>
|
||||
<td bitCell>
|
||||
<a
|
||||
@@ -26,7 +28,51 @@
|
||||
>
|
||||
</td>
|
||||
<td bitCell>{{ i.amount | currency: "$" }}</td>
|
||||
<td bitCell class="tw-w-28">
|
||||
<span *ngIf="i.paid">
|
||||
<i class="bwi bwi-check tw-text-success" aria-hidden="true"></i>
|
||||
{{ "paid" | i18n }}
|
||||
</span>
|
||||
<span *ngIf="!i.paid">
|
||||
<i class="bwi bwi-exclamation-circle tw-text-muted" aria-hidden="true"></i>
|
||||
{{ "unpaid" | i18n }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</bit-section>
|
||||
<bit-section>
|
||||
<h3 bitTypography="h3">{{ "paid" | i18n }} {{ "invoices" | i18n }}</h3>
|
||||
<p bitTypography="body1" *ngIf="!paidInvoices || !paidInvoices.length">
|
||||
{{ "noPaidInvoices" | i18n }}
|
||||
</p>
|
||||
<bit-table>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let i of paidInvoices">
|
||||
<td bitCell>{{ i.date | date: "mediumDate" }}</td>
|
||||
<td bitCell>
|
||||
<a
|
||||
href="{{ i.pdfUrl }}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="tw-mr-2"
|
||||
appA11yTitle="{{ 'downloadInvoice' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-file-pdf" aria-hidden="true"></i
|
||||
></a>
|
||||
<a
|
||||
bitLink
|
||||
href="{{ i.url }}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
title="{{ 'viewInvoice' | i18n }}"
|
||||
>
|
||||
{{ "invoiceNumber" | i18n: i.number }}</a
|
||||
>
|
||||
</td>
|
||||
<td bitCell>{{ i.amount | currency: "$" }}</td>
|
||||
<td bitCell class="tw-w-28">
|
||||
<span *ngIf="i.paid">
|
||||
<i class="bwi bwi-check tw-text-success" aria-hidden="true"></i>
|
||||
{{ "paid" | i18n }}
|
||||
|
||||
@@ -12,7 +12,10 @@ import {
|
||||
})
|
||||
export class BillingHistoryComponent {
|
||||
@Input()
|
||||
invoices: BillingInvoiceResponse[];
|
||||
openInvoices: BillingInvoiceResponse[];
|
||||
|
||||
@Input()
|
||||
paidInvoices: BillingInvoiceResponse[];
|
||||
|
||||
@Input()
|
||||
transactions: BillingTransactionResponse[];
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
unauthGuardFn,
|
||||
} from "@bitwarden/angular/auth/guards";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import { generatorSwap } from "@bitwarden/angular/tools/generator/generator-swap";
|
||||
import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap";
|
||||
import {
|
||||
AnonLayoutWrapperComponent,
|
||||
@@ -72,6 +73,7 @@ import { RequestSMAccessComponent } from "./secrets-manager/secrets-manager-land
|
||||
import { SMLandingComponent } from "./secrets-manager/secrets-manager-landing/sm-landing.component";
|
||||
import { DomainRulesComponent } from "./settings/domain-rules.component";
|
||||
import { PreferencesComponent } from "./settings/preferences.component";
|
||||
import { CredentialGeneratorComponent } from "./tools/credential-generator/credential-generator.component";
|
||||
import { GeneratorComponent } from "./tools/generator.component";
|
||||
import { ReportsModule } from "./tools/reports";
|
||||
import { AccessComponent } from "./tools/send/access.component";
|
||||
@@ -645,11 +647,10 @@ const routes: Routes = [
|
||||
titleId: "exportVault",
|
||||
} satisfies RouteDataProperties,
|
||||
},
|
||||
{
|
||||
...generatorSwap(GeneratorComponent, CredentialGeneratorComponent, {
|
||||
path: "generator",
|
||||
component: GeneratorComponent,
|
||||
data: { titleId: "generator" } satisfies RouteDataProperties,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { unauthGuardFn } from "@bitwarden/angular/auth/guards";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
import { AccessIntelligenceComponent } from "./access-intelligence.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: AccessIntelligenceComponent,
|
||||
canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence), unauthGuardFn()],
|
||||
data: {
|
||||
titleId: "accessIntelligence",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AccessIntelligenceRoutingModule {}
|
||||
@@ -0,0 +1,23 @@
|
||||
<app-header></app-header>
|
||||
<bit-tab-group [(selectedIndex)]="tabIndex">
|
||||
<bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
|
||||
<h2 bitTypography="h2">{{ "allApplications" | i18n }}</h2>
|
||||
<tools-application-table></tools-application-table>
|
||||
</bit-tab>
|
||||
<bit-tab>
|
||||
<ng-template bitTabLabel>
|
||||
<i class="bwi bwi-star"></i>
|
||||
{{ "priorityApplicationsWithCount" | i18n: priorityApps.length }}
|
||||
</ng-template>
|
||||
<h2 bitTypography>{{ "priorityApplications" | i18n }}</h2>
|
||||
<tools-application-table></tools-application-table>
|
||||
</bit-tab>
|
||||
<bit-tab>
|
||||
<ng-template bitTabLabel>
|
||||
<i class="bwi bwi-envelope"></i>
|
||||
{{ "notifiedMembersWithCount" | i18n: priorityApps.length }}
|
||||
</ng-template>
|
||||
<h2 bitTypography="h2">{{ "notifiedMembers" | i18n }}</h2>
|
||||
<tools-notified-members-table></tools-notified-members-table>
|
||||
</bit-tab>
|
||||
</bit-tab-group>
|
||||
@@ -0,0 +1,45 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { first } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { TabsModule } from "@bitwarden/components";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
|
||||
import { ApplicationTableComponent } from "./application-table.component";
|
||||
import { NotifiedMembersTableComponent } from "./notified-members-table.component";
|
||||
|
||||
export enum AccessIntelligenceTabType {
|
||||
AllApps = 0,
|
||||
PriorityApps = 1,
|
||||
NotifiedMembers = 2,
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: "./access-intelligence.component.html",
|
||||
imports: [
|
||||
ApplicationTableComponent,
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
HeaderModule,
|
||||
NotifiedMembersTableComponent,
|
||||
TabsModule,
|
||||
],
|
||||
})
|
||||
export class AccessIntelligenceComponent {
|
||||
tabIndex: AccessIntelligenceTabType;
|
||||
|
||||
apps: any[] = [];
|
||||
priorityApps: any[] = [];
|
||||
notifiedMembers: any[] = [];
|
||||
|
||||
constructor(route: ActivatedRoute) {
|
||||
route.queryParams.pipe(takeUntilDestroyed(), first()).subscribe(({ tabIndex }) => {
|
||||
this.tabIndex = !isNaN(tabIndex) ? tabIndex : AccessIntelligenceTabType.AllApps;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { AccessIntelligenceRoutingModule } from "./access-intelligence-routing.module";
|
||||
import { AccessIntelligenceComponent } from "./access-intelligence.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [AccessIntelligenceComponent, AccessIntelligenceRoutingModule],
|
||||
})
|
||||
export class AccessIntelligenceModule {}
|
||||
@@ -0,0 +1,11 @@
|
||||
<!-- <bit-table [dataSource]="dataSource"> -->
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell>{{ "application" | i18n }}</th>
|
||||
<th bitCell>{{ "atRiskPasswords" | i18n }}</th>
|
||||
<th bitCell>{{ "totalPasswords" | i18n }}</th>
|
||||
<th bitCell>{{ "atRiskMembers" | i18n }}</th>
|
||||
<th bitCell>{{ "totalMembers" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<!-- </bit-table> -->
|
||||
@@ -0,0 +1,19 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { TableDataSource, TableModule } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "tools-application-table",
|
||||
templateUrl: "./application-table.component.html",
|
||||
imports: [CommonModule, JslibModule, TableModule],
|
||||
})
|
||||
export class ApplicationTableComponent {
|
||||
protected dataSource = new TableDataSource<any>();
|
||||
|
||||
constructor() {
|
||||
this.dataSource.data = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<!-- <bit-table [dataSource]="dataSource"> -->
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell>{{ "member" | i18n }}</th>
|
||||
<th bitCell>{{ "atRiskPasswords" | i18n }}</th>
|
||||
<th bitCell>{{ "totalPasswords" | i18n }}</th>
|
||||
<th bitCell>{{ "atRiskApplications" | i18n }}</th>
|
||||
<th bitCell>{{ "totalApplications" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<!-- </bit-table> -->
|
||||
@@ -0,0 +1,19 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { TableDataSource, TableModule } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "tools-notified-members-table",
|
||||
templateUrl: "./notified-members-table.component.html",
|
||||
imports: [CommonModule, JslibModule, TableModule],
|
||||
})
|
||||
export class NotifiedMembersTableComponent {
|
||||
dataSource = new TableDataSource<any>();
|
||||
|
||||
constructor() {
|
||||
this.dataSource.data = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<tools-credential-generator />
|
||||
</bit-container>
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { GeneratorModule } from "@bitwarden/generator-components";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "credential-generator",
|
||||
templateUrl: "credential-generator.component.html",
|
||||
imports: [SharedModule, HeaderModule, GeneratorModule],
|
||||
})
|
||||
export class CredentialGeneratorComponent {}
|
||||
@@ -788,8 +788,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the given cipher
|
||||
* @param cipherView - The cipher to be edited
|
||||
* Edit the given cipher or add a new cipher
|
||||
* @param cipherView - When set, the cipher to be edited
|
||||
* @param cloneCipher - `true` when the cipher should be cloned.
|
||||
* Used in place of the `additionalComponentParameters`, as
|
||||
* the `editCipherIdV2` method has a differing implementation.
|
||||
@@ -797,7 +797,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
* the `AddEditComponent` to edit methods directly.
|
||||
*/
|
||||
async editCipher(
|
||||
cipher: CipherView,
|
||||
cipher: CipherView | null,
|
||||
cloneCipher: boolean,
|
||||
additionalComponentParameters?: (comp: AddEditComponent) => void,
|
||||
) {
|
||||
@@ -805,7 +805,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async editCipherId(
|
||||
cipher: CipherView,
|
||||
cipher: CipherView | null,
|
||||
cloneCipher: boolean,
|
||||
additionalComponentParameters?: (comp: AddEditComponent) => void,
|
||||
) {
|
||||
@@ -827,7 +827,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
const defaultComponentParameters = (comp: AddEditComponent) => {
|
||||
comp.organization = this.organization;
|
||||
comp.organizationId = this.organization.id;
|
||||
comp.cipherId = cipher.id;
|
||||
comp.cipherId = cipher?.id;
|
||||
comp.onSavedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||
modal.close();
|
||||
this.refresh();
|
||||
@@ -866,10 +866,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
* Edit a cipher using the new AddEditCipherDialogV2 component.
|
||||
* Only to be used behind the ExtensionRefresh feature flag.
|
||||
*/
|
||||
private async editCipherIdV2(cipher: CipherView, cloneCipher: boolean) {
|
||||
private async editCipherIdV2(cipher: CipherView | null, cloneCipher: boolean) {
|
||||
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
|
||||
cloneCipher ? "clone" : "edit",
|
||||
cipher.id as CipherId,
|
||||
cipher?.id as CipherId | null,
|
||||
);
|
||||
|
||||
await this.openVaultItemDialog("form", cipherFormConfig, cipher);
|
||||
|
||||
@@ -1,4 +1,64 @@
|
||||
{
|
||||
"allApplications": {
|
||||
"message": "All applications"
|
||||
},
|
||||
"priorityApplications": {
|
||||
"message": "Priority applications"
|
||||
},
|
||||
"accessIntelligence": {
|
||||
"message": "Access Intelligence"
|
||||
},
|
||||
"notifiedMembers": {
|
||||
"message": "Notified members"
|
||||
},
|
||||
"allApplicationsWithCount": {
|
||||
"message": "All applications ($COUNT$)",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"priorityApplicationsWithCount": {
|
||||
"message": "Priority applications ($COUNT$)",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifiedMembersWithCount": {
|
||||
"message": "Notified members ($COUNT$)",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"application": {
|
||||
"message": "Application"
|
||||
},
|
||||
"atRiskPasswords": {
|
||||
"message": "At-risk passwords"
|
||||
},
|
||||
"totalPasswords": {
|
||||
"message": "Total passwords"
|
||||
},
|
||||
"atRiskMembers": {
|
||||
"message": "At-risk members"
|
||||
},
|
||||
"totalMembers": {
|
||||
"message": "Total members"
|
||||
},
|
||||
"atRiskApplications": {
|
||||
"message": "At-risk applications"
|
||||
},
|
||||
"totalApplications": {
|
||||
"message": "Total applications"
|
||||
},
|
||||
"whatTypeOfItem": {
|
||||
"message": "What type of item is this?"
|
||||
},
|
||||
@@ -1452,7 +1512,12 @@
|
||||
"description": "Minimum special characters"
|
||||
},
|
||||
"ambiguous": {
|
||||
"message": "Avoid ambiguous characters"
|
||||
"message": "Avoid ambiguous characters",
|
||||
"description": "deprecated. Use avoidAmbiguous instead."
|
||||
},
|
||||
"avoidAmbiguous": {
|
||||
"message": "Avoid ambiguous characters",
|
||||
"description": "Label for the avoid ambiguous characters checkbox."
|
||||
},
|
||||
"regeneratePassword": {
|
||||
"message": "Regenerate password"
|
||||
@@ -1465,18 +1530,51 @@
|
||||
},
|
||||
"uppercase": {
|
||||
"message": "Uppercase (A-Z)",
|
||||
"description": "Include uppercase letters in the password generator."
|
||||
"description": "deprecated. Use uppercaseLabel instead."
|
||||
},
|
||||
"lowercase": {
|
||||
"message": "Lowercase (a-z)",
|
||||
"description": "Include lowercase letters in the password generator."
|
||||
"description": "deprecated. Use lowercaseLabel instead."
|
||||
},
|
||||
"numbers": {
|
||||
"message": "Numbers (0-9)"
|
||||
"message": "Numbers (0-9)",
|
||||
"description": "deprecated. Use numbersLabel instead."
|
||||
},
|
||||
"specialCharacters": {
|
||||
"message": "Special characters (!@#$%^&*)"
|
||||
},
|
||||
"uppercaseDescription": {
|
||||
"message": "Include uppercase characters",
|
||||
"description": "Tooltip for the password generator uppercase character checkbox"
|
||||
},
|
||||
"uppercaseLabel": {
|
||||
"message": "A-Z",
|
||||
"description": "Label for the password generator uppercase character checkbox"
|
||||
},
|
||||
"lowercaseDescription": {
|
||||
"message": "Include lowercase characters",
|
||||
"description": "Full description for the password generator lowercase character checkbox"
|
||||
},
|
||||
"lowercaseLabel": {
|
||||
"message": "a-z",
|
||||
"description": "Label for the password generator lowercase character checkbox"
|
||||
},
|
||||
"numbersDescription": {
|
||||
"message": "Include numbers",
|
||||
"description": "Full description for the password generator numbers checkbox"
|
||||
},
|
||||
"numbersLabel": {
|
||||
"message": "0-9",
|
||||
"description": "Label for the password generator numbers checkbox"
|
||||
},
|
||||
"specialCharactersDescription": {
|
||||
"message": "Include special characters",
|
||||
"description": "Full description for the password generator special characters checkbox"
|
||||
},
|
||||
"specialCharactersLabel": {
|
||||
"message": "!@#$%^&*",
|
||||
"description": "Label for the password generator special characters checkbox"
|
||||
},
|
||||
"numWords": {
|
||||
"message": "Number of words"
|
||||
},
|
||||
@@ -2628,8 +2726,11 @@
|
||||
"invoices": {
|
||||
"message": "Invoices"
|
||||
},
|
||||
"noInvoices": {
|
||||
"message": "No invoices."
|
||||
"noUnpaidInvoices": {
|
||||
"message": "No unpaid invoices."
|
||||
},
|
||||
"noPaidInvoices": {
|
||||
"message": "No paid invoices."
|
||||
},
|
||||
"paid": {
|
||||
"message": "Paid",
|
||||
@@ -6235,7 +6336,8 @@
|
||||
"message": "Account settings"
|
||||
},
|
||||
"generator": {
|
||||
"message": "Generator"
|
||||
"message": "Generator",
|
||||
"description": "Short for 'credential generator'."
|
||||
},
|
||||
"whatWouldYouLikeToGenerate": {
|
||||
"message": "What would you like to generate?"
|
||||
|
||||
Reference in New Issue
Block a user