From e357819251c241661e92f99af54126e0885405b3 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:00:06 +0200 Subject: [PATCH] [PM-4197] Enable importing on deskop (#6502) * Split up import/export into separate modules * Fix routing and apply PR feedback * Renamed OrganizationExport exports to OrganizationVaultExport * Make import dialogs standalone and move them to libs/importer * Make import.component re-usable - Move functionality which was previously present on the org-import.component into import.component - Move import.component into libs/importer Make import.component standalone Create import-web.component to represent Web UI Fix module imports and routing Remove unused org-import-files * Enable importing on deskop Create import-dialog Create file-menu entry to open import-dialog Extend messages.json to include all the necessary messages from shared components * Renamed filenames according to export rename * Make ImportWebComponent standalone, simplify routing * Pass organizationId as Input to ImportComponent * use formLoading and formDisabled outputs * use formLoading & formDisabled in desktop * Emit an event when the import succeeds Remove Angular router from base-component as other clients might not have routing (i.e. desktop) Move logic that happened on web successful import into the import-web.component * Enable importing on deskop Create import-dialog Create file-menu entry to open import-dialog Extend messages.json to include all the necessary messages from shared components * use formLoading & formDisabled in desktop * Add missing message for importBlockedByPolicy callout * Remove commented code for submit button * Implement onSuccessfulImport to close dialog on success * fix table themes on desktop & browser * fix fileSelector button styles * update selectors to use tools prefix; remove unused selectors * update selectors * Wall off UI components in libs/importer Create barrel-file for libs/importer/components Remove components and dialog exports from libs/importer/index.ts Extend libs/shared/tsconfig.libs.json to include @bitwarden/importer/ui -> libs/importer/components Extend apps/web/tsconfig.ts to include @bitwarden/importer/ui Update all usages * Rename @bitwarden/importer to @bitwarden/importer/core Create more barrel files in libs/importer/* Update imports within libs/importer Extend tsconfig files Update imports in web, desktop, browser and cli * Lazy-load the ImportWebComponent via both routes * Fix import path for ImportComponent * Use SharedModule as import in import-web.component * File selector should be displayed as secondary * Add missing messages for file-password-prompt * Add missing messages for import-error-dialog * Add missing message for import-success-dialog * Use bitSubmit to override submit preventDefault (#6607) Co-authored-by: Daniel James Smith * Use large dialogSize * PM-4398 - Add missing importWarning --------- Co-authored-by: Daniel James Smith Co-authored-by: William Martin --- apps/desktop/src/app/app.component.ts | 4 + .../import/import-desktop.component.html | 26 ++++ .../tools/import/import-desktop.component.ts | 33 ++++++ apps/desktop/src/locales/en/messages.json | 111 ++++++++++++++++++ apps/desktop/src/main/menu/menu.file.ts | 10 ++ 5 files changed, 184 insertions(+) create mode 100644 apps/desktop/src/app/tools/import/import-desktop.component.html create mode 100644 apps/desktop/src/app/tools/import/import-desktop.component.ts diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 65ac83b59f..b98dd0a839 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -55,6 +55,7 @@ import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.compo import { SettingsComponent } from "./accounts/settings.component"; import { ExportComponent } from "./tools/export/export.component"; import { GeneratorComponent } from "./tools/generator.component"; +import { ImportDesktopComponent } from "./tools/import/import-desktop.component"; import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component"; const BroadcasterSubscriptionId = "AppComponent"; @@ -328,6 +329,9 @@ export class AppComponent implements OnInit, OnDestroy { } this.messagingService.send("scheduleNextSync"); break; + case "importVault": + await this.dialogService.open(ImportDesktopComponent); + break; case "exportVault": await this.openExportVault(); break; diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.html b/apps/desktop/src/app/tools/import/import-desktop.component.html new file mode 100644 index 0000000000..74d4098255 --- /dev/null +++ b/apps/desktop/src/app/tools/import/import-desktop.component.html @@ -0,0 +1,26 @@ + + {{ "importData" | i18n }} + + + + + + + + diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.ts b/apps/desktop/src/app/tools/import/import-desktop.component.ts new file mode 100644 index 0000000000..62fc007731 --- /dev/null +++ b/apps/desktop/src/app/tools/import/import-desktop.component.ts @@ -0,0 +1,33 @@ +import { DialogRef } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; +import { ImportComponent } from "@bitwarden/importer/ui"; + +@Component({ + templateUrl: "import-desktop.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + DialogModule, + AsyncActionsModule, + ButtonModule, + ImportComponent, + ], +}) +export class ImportDesktopComponent { + protected disabled = false; + protected loading = false; + + constructor(public dialogRef: DialogRef) {} + + /** + * Callback that is called after a successful import. + */ + protected async onSuccessfulImport(organizationId: string): Promise { + this.dialogRef.close(); + } +} diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 52fdbf1b56..537be07da9 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1662,6 +1662,9 @@ "personalOwnershipPolicyInEffect": { "message": "An organization policy is affecting your ownership options." }, + "personalOwnershipPolicyInEffectImports": { + "message": "An organization policy has blocked importing items into your individual vault." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2427,5 +2430,113 @@ }, "aliasDomain": { "message": "Alias domain" + }, + "importData": { + "message": "Import data", + "description": "Used for the desktop menu item and the header of the import dialog" + }, + "importError": { + "message": "Import error" + }, + "importErrorDesc": { + "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + }, + "resolveTheErrorsBelowAndTryAgain": { + "message": "Resolve the errors below and try again." + }, + "description": { + "message": "Description" + }, + "importSuccess": { + "message": "Data successfully imported" + }, + "importSuccessNumberOfItems": { + "message": "A total of $AMOUNT$ items were imported.", + "placeholders": { + "amount": { + "content": "$1", + "example": "2" + } + } + }, + "total": { + "message": "Total" + }, + "importWarning": { + "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "importFormatError": { + "message": "Data is not formatted correctly. Please check your import file and try again." + }, + "importNothingError": { + "message": "Nothing was imported." + }, + "importEncKeyError": { + "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + }, + "importDestination": { + "message": "Import destination" + }, + "learnAboutImportOptions": { + "message": "Learn about your import options" + }, + "selectImportFolder": { + "message": "Select a folder" + }, + "selectImportCollection": { + "message": "Select a collection" + }, + "importTargetHint": { + "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", + "placeholders": { + "destination": { + "content": "$1", + "example": "folder or collection" + } + } + }, + "importUnassignedItemsError": { + "message": "File contains unassigned items." + }, + "selectFormat": { + "message": "Select the format of the import file" + }, + "selectImportFile": { + "message": "Select the import file" + }, + "chooseFile": { + "message": "Choose File" + }, + "noFileChosen": { + "message": "No file chosen" + }, + "orCopyPasteFileContents": { + "message": "or copy/paste the import file contents" + }, + "instructionsFor": { + "message": "$NAME$ Instructions", + "description": "The title for the import tool instructions.", + "placeholders": { + "name": { + "content": "$1", + "example": "LastPass (csv)" + } + } + }, + "confirmVaultImport": { + "message": "Confirm vault import" + }, + "confirmVaultImportDesc": { + "message": "This file is password-protected. Please enter the file password to import data." + }, + "confirmFilePassword": { + "message": "Confirm file password" } } diff --git a/apps/desktop/src/main/menu/menu.file.ts b/apps/desktop/src/main/menu/menu.file.ts index 173b6066ab..0782039d7c 100644 --- a/apps/desktop/src/main/menu/menu.file.ts +++ b/apps/desktop/src/main/menu/menu.file.ts @@ -24,6 +24,7 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { this.addNewFolder, this.separator, this.syncVault, + this.importVault, this.exportVault, ]; @@ -123,6 +124,15 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { }; } + private get importVault(): MenuItemConstructorOptions { + return { + id: "importVault", + label: this.localize("importData"), + click: () => this.sendMessage("importVault"), + enabled: !this._isLocked, + }; + } + private get exportVault(): MenuItemConstructorOptions { return { id: "exportVault",