1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 13:53:34 +00:00

[PM-1925][PM-2741][AC-1334] flexible collections export page (#5759)

* Use bitTypography for page title

* Replaced app-callout with bit-callout

* Replace button with bit-button

* Update radio buttons to use CL

* Use searchable select for fileFormat dropdown

* Remove unneeded divs (old styling)

* pm-1826 remove eslint-disable tailwindcss/no-custom-classname

* Removed for-attribute from bit-labels

* Removed bitInput from bit-selects

* Removed name-attribute from bit-selects

* Make format a required field

* Removed unused dependency on cryptoService

* Remove unused dependency on BroadcasterService

* Removed dependency on window

* Moved organizationId into BaseExportComponent

* Add vaultSelector

Add organizationService as new dependency
Retrieve organizations a user has access to
Add vaultSelector dropdown
Add `export from` label
Add exportFromHint

* Removed hint as discussed by product&design

* Add function to check for import/export permission

* Export callout should listen to changes

Even though the organizationId was changed, the Input did not trigger changing the scope

* Reading FlexibleCollections feature flag to show the vault-selector on export (#7196)

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: aj-rosado <109146700+aj-rosado@users.noreply.github.com>
This commit is contained in:
Daniel James Smith
2023-12-14 13:55:54 +01:00
committed by GitHub
parent 12de4b1386
commit 60d9f3d150
8 changed files with 180 additions and 158 deletions

View File

@@ -3,10 +3,11 @@ import { UntypedFormBuilder } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { EventType } from "@bitwarden/common/enums";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -23,7 +24,6 @@ import { ExportComponent } from "../../../../tools/vault-export/export.component
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class OrganizationVaultExportComponent extends ExportComponent {
constructor(
cryptoService: CryptoService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
exportService: VaultExportServiceAbstraction,
@@ -35,9 +35,10 @@ export class OrganizationVaultExportComponent extends ExportComponent {
formBuilder: UntypedFormBuilder,
fileDownloadService: FileDownloadService,
dialogService: DialogService,
organizationService: OrganizationService,
configService: ConfigServiceAbstraction,
) {
super(
cryptoService,
i18nService,
platformUtilsService,
exportService,
@@ -48,6 +49,8 @@ export class OrganizationVaultExportComponent extends ExportComponent {
formBuilder,
fileDownloadService,
dialogService,
organizationService,
configService,
);
}

View File

@@ -1,5 +1,3 @@
<!-- Please remove this disable statement when editing this file! -->
<!-- eslint-disable tailwindcss/no-custom-classname -->
<form
#form
(ngSubmit)="submit()"
@@ -7,137 +5,112 @@
[formGroup]="exportForm"
*ngIf="exportForm"
>
<div class="page-header">
<h1>{{ "exportVault" | i18n }}</h1>
</div>
<h1 bitTypography="h1">{{ "exportVault" | i18n }}</h1>
<app-callout type="error" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
<bit-callout type="danger" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
{{ "personalVaultExportPolicyInEffect" | i18n }}
</app-callout>
</bit-callout>
<app-export-scope-callout
[organizationId]="organizationId"
*ngIf="!disabledByPolicy"
></app-export-scope-callout>
<div class="row">
<div class="col-6">
<bit-form-field>
<bit-label class="tw-text-lg" for="format">{{ "fileFormat" | i18n }}</bit-label>
<select bitInput name="format" formControlName="format">
<option *ngFor="let f of formatOptions" [value]="f.value">{{ f.name }}</option>
</select>
</bit-form-field>
</div>
</div>
<div class="row">
<div class="form-group col-6">
<ng-container *ngIf="format === 'encrypted_json'">
<div role="radiogroup" aria-labelledby="exportTypeHeading">
<label id="exportTypeHeading" class="tw-semi-bold tw-text-lg">
{{ "exportTypeHeading" | i18n }}
</label>
<bit-form-field *ngIf="flexibleCollectionsEnabled$ | async">
<bit-label>{{ "exportFrom" | i18n }}</bit-label>
<bit-select formControlName="vaultSelector">
<bit-option [label]="'myVault' | i18n" value="myVault" icon="bwi-user" />
<bit-option
*ngFor="let o of organizations$ | async"
[value]="o.id"
[label]="o.name"
icon="bwi-business"
/>
</bit-select>
</bit-form-field>
<div appBoxRow name="FileTypeOptions" class="tw-flex tw-items-center">
<div>
<input
type="radio"
class="radio"
name="fileEncryptionType"
id="AccountEncrypted"
[value]="encryptedExportType.AccountEncrypted"
formControlName="fileEncryptionType"
[checked]="fileEncryptionType === encryptedExportType.AccountEncrypted"
/>
</div>
<div>
<label class="tw-semi-bold tw-text-md tw-my-1 tw-ml-1" for="AccountEncrypted">
{{ "accountRestricted" | i18n }}
</label>
</div>
</div>
<bit-form-field>
<bit-label>{{ "fileFormat" | i18n }}</bit-label>
<bit-select formControlName="format">
<bit-option *ngFor="let f of formatOptions" [value]="f.value" [label]="f.name" />
</bit-select>
</bit-form-field>
<div class="tw-regular ml-3 pb-2 tw-text-sm">
{{ "accountRestrictedOptionDescription" | i18n }}
</div>
<ng-container *ngIf="format === 'encrypted_json'">
<bit-radio-group formControlName="fileEncryptionType" aria-label="exportTypeHeading">
<bit-label>{{ "exportTypeHeading" | i18n }}</bit-label>
<div class="tw-flex tw-items-center">
<div>
<input
type="radio"
class="radio"
name="fileEncryptionType"
id="FileEncrypted"
[value]="encryptedExportType.FileEncrypted"
formControlName="fileEncryptionType"
[checked]="fileEncryptionType === encryptedExportType.FileEncrypted"
/>
</div>
<div>
<label class="tw-semi-bold tw-text-md tw-my-1 tw-ml-1" for="FileEncrypted">{{
"passwordProtected" | i18n
}}</label>
</div>
</div>
<div class="tw-regular ml-3 tw-text-sm">
{{ "passwordProtectedOptionDescription" | i18n }}
</div>
</div>
<br />
<ng-container *ngIf="fileEncryptionType == encryptedExportType.FileEncrypted">
<div class="tw-mb-3">
<bit-form-field>
<bit-label>{{ "filePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="filePassword"
formControlName="filePassword"
name="password"
/>
<button
type="button"
bitSuffix
bitIconButton
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
</bit-form-field>
<app-password-strength [password]="filePassword" [showText]="true">
</app-password-strength>
</div>
<bit-form-field>
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="confirmFilePassword"
formControlName="confirmFilePassword"
name="confirmFilePassword"
/>
<button
type="button"
bitSuffix
bitIconButton
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
</bit-form-field>
</ng-container>
</ng-container>
<button
type="submit"
class="btn btn-primary btn-submit"
[disabled]="form.loading || disabledByPolicy"
[ngClass]="{ manual: disabledByPolicy }"
<bit-radio-button
id="AccountEncrypted"
name="fileEncryptionType"
class="tw-block"
[value]="encryptedExportType.AccountEncrypted"
checked="fileEncryptionType === encryptedExportType.AccountEncrypted"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "confirmFormat" | i18n }}</span>
</button>
</div>
</div>
<bit-label>{{ "accountRestricted" | i18n }}</bit-label>
<bit-hint>{{ "accountRestrictedOptionDescription" | i18n }}</bit-hint>
</bit-radio-button>
<bit-radio-button
id="FileEncrypted"
name="fileEncryptionType"
class="tw-block"
[value]="encryptedExportType.FileEncrypted"
checked="fileEncryptionType === encryptedExportType.FileEncrypted"
>
<bit-label>{{ "passwordProtected" | i18n }}</bit-label>
<bit-hint>{{ "passwordProtectedOptionDescription" | i18n }}</bit-hint>
</bit-radio-button>
</bit-radio-group>
<ng-container *ngIf="fileEncryptionType == encryptedExportType.FileEncrypted">
<div class="tw-mb-3">
<bit-form-field>
<bit-label>{{ "filePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="filePassword"
formControlName="filePassword"
name="password"
/>
<button
type="button"
bitSuffix
bitIconButton
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
</bit-form-field>
<app-password-strength [password]="filePassword" [showText]="true"> </app-password-strength>
</div>
<bit-form-field>
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="confirmFilePassword"
formControlName="confirmFilePassword"
name="confirmFilePassword"
/>
<button
type="button"
bitSuffix
bitIconButton
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
</bit-form-field>
</ng-container>
</ng-container>
<button
bitButton
type="submit"
buttonType="primary"
[loading]="form.loading"
[disabled]="disabledByPolicy"
>
{{ "confirmFormat" | i18n }}
</button>
</form>

View File

@@ -4,9 +4,11 @@ import { firstValueFrom } from "rxjs";
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -22,12 +24,15 @@ import { openUserVerificationPrompt } from "../../auth/shared/components/user-ve
templateUrl: "export.component.html",
})
export class ExportComponent extends BaseExportComponent {
organizationId: string;
encryptedExportType = EncryptedExportType;
protected showFilePassword: boolean;
protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollections,
false,
);
constructor(
cryptoService: CryptoService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
exportService: VaultExportServiceAbstraction,
@@ -38,20 +43,21 @@ export class ExportComponent extends BaseExportComponent {
formBuilder: UntypedFormBuilder,
fileDownloadService: FileDownloadService,
dialogService: DialogService,
organizationService: OrganizationService,
protected configService: ConfigServiceAbstraction,
) {
super(
cryptoService,
i18nService,
platformUtilsService,
exportService,
eventCollectionService,
policyService,
window,
logService,
userVerificationService,
formBuilder,
fileDownloadService,
dialogService,
organizationService,
);
}

View File

@@ -1074,6 +1074,9 @@
"export": {
"message": "Export"
},
"exportFrom": {
"message": "Export from"
},
"exportVault": {
"message": "Export vault"
},