diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 7a0c059a5c..64f039bb8b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3107,6 +3107,9 @@ "confirmFilePassword": { "message": "Confirm file password" }, + "exportSuccess": { + "message": "Vault data exported" + }, "typePasskey": { "message": "Passkey" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 7c72ea58fb..333a1c0e7b 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2843,6 +2843,9 @@ "confirmFilePassword": { "message": "Confirm file password" }, + "exportSuccess": { + "message": "Vault data exported" + }, "multifactorAuthenticationCancelled": { "message": "Multifactor authentication cancelled" }, diff --git a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts index 772e70fc12..cc65bef8c7 100644 --- a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts @@ -56,12 +56,14 @@ const routes: Routes = [ }, { path: "export", - loadChildren: () => - import("../tools/vault-export/org-vault-export.module").then( - (m) => m.OrganizationVaultExportModule, + loadComponent: () => + import("../tools/vault-export/org-vault-export.component").then( + (mod) => mod.OrganizationVaultExportComponent, ), + canActivate: [OrganizationPermissionsGuard], data: { titleId: "exportVault", + organizationPermissions: (org: Organization) => org.canAccessImportExport, }, }, ], diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export-routing.module.ts b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export-routing.module.ts deleted file mode 100644 index e3e809a550..0000000000 --- a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export-routing.module.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; - -import { OrganizationPermissionsGuard } from "../../guards/org-permissions.guard"; - -import { OrganizationVaultExportComponent } from "./org-vault-export.component"; - -const routes: Routes = [ - { - path: "", - component: OrganizationVaultExportComponent, - canActivate: [OrganizationPermissionsGuard], - data: { - titleId: "exportVault", - organizationPermissions: (org: Organization) => org.canAccessImportExport, - }, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], -}) -export class OrganizationVaultExportRoutingModule {} diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.html b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.html new file mode 100644 index 0000000000..01975272e7 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.html @@ -0,0 +1,21 @@ + + + + + + diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts index 0caf39ea79..16e9f76d21 100644 --- a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts @@ -1,83 +1,28 @@ -import { Component } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; +import { Component, OnInit } from "@angular/core"; 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 { EventType } from "@bitwarden/common/enums"; -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"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; +import { ExportComponent } from "@bitwarden/vault-export-ui"; -import { ExportComponent } from "../../../../tools/vault-export/export.component"; +import { LooseComponentsModule, SharedModule } from "../../../../shared"; @Component({ - selector: "app-org-export", - templateUrl: "../../../../tools/vault-export/export.component.html", + templateUrl: "org-vault-export.component.html", + standalone: true, + imports: [SharedModule, ExportComponent, LooseComponentsModule], }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class OrganizationVaultExportComponent extends ExportComponent { - constructor( - i18nService: I18nService, - toastService: ToastService, - exportService: VaultExportServiceAbstraction, - eventCollectionService: EventCollectionService, - private route: ActivatedRoute, - policyService: PolicyService, - logService: LogService, - formBuilder: UntypedFormBuilder, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - organizationService: OrganizationService, - ) { - super( - i18nService, - toastService, - exportService, - eventCollectionService, - policyService, - logService, - formBuilder, - fileDownloadService, - dialogService, - organizationService, - ); - } +export class OrganizationVaultExportComponent implements OnInit { + protected routeOrgId: string = null; + protected loading = false; + protected disabled = false; - protected get disabledByPolicy(): boolean { - return false; - } + constructor(private route: ActivatedRoute) {} async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - this.organizationId = params.organizationId; - }); - - await super.ngOnInit(); + this.routeOrgId = this.route.snapshot.paramMap.get("organizationId"); } - getExportData() { - return this.exportService.getOrganizationExport( - this.organizationId, - this.format, - this.filePassword, - ); - } - - getFileName() { - return super.getFileName("org"); - } - - async collectEvent(): Promise { - await this.eventCollectionService.collect( - EventType.Organization_ClientExportedVault, - null, - null, - this.organizationId, - ); - } + /** + * Callback that is called after a successful export. + */ + protected async onSuccessfulExport(organizationId: string): Promise {} } diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.module.ts b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.module.ts deleted file mode 100644 index ca8a75165b..0000000000 --- a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui"; - -import { LooseComponentsModule, SharedModule } from "../../../../shared"; - -import { OrganizationVaultExportRoutingModule } from "./org-vault-export-routing.module"; -import { OrganizationVaultExportComponent } from "./org-vault-export.component"; - -@NgModule({ - imports: [ - SharedModule, - LooseComponentsModule, - OrganizationVaultExportRoutingModule, - ExportScopeCalloutComponent, - ], - declarations: [OrganizationVaultExportComponent], -}) -export class OrganizationVaultExportModule {} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index e7236348a6..dee6228530 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -448,8 +448,13 @@ const routes: Routes = [ }, { path: "export", - loadChildren: () => - import("./tools/vault-export/export.module").then((m) => m.ExportModule), + loadComponent: () => + import("./tools/vault-export/export-web.component").then( + (mod) => mod.ExportWebComponent, + ), + data: { + titleId: "exportVault", + } satisfies DataProperties, }, { path: "generator", diff --git a/apps/web/src/app/tools/vault-export/export-routing.module.ts b/apps/web/src/app/tools/vault-export/export-routing.module.ts deleted file mode 100644 index 3afda4a06f..0000000000 --- a/apps/web/src/app/tools/vault-export/export-routing.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; - -import { ExportComponent } from "./export.component"; - -const routes: Routes = [ - { - path: "", - component: ExportComponent, - data: { titleId: "exportVault" }, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], -}) -export class ExportRoutingModule {} diff --git a/apps/web/src/app/tools/vault-export/export-web.component.html b/apps/web/src/app/tools/vault-export/export-web.component.html new file mode 100644 index 0000000000..e3d0ca75d2 --- /dev/null +++ b/apps/web/src/app/tools/vault-export/export-web.component.html @@ -0,0 +1,20 @@ + + + + + + diff --git a/apps/web/src/app/tools/vault-export/export-web.component.ts b/apps/web/src/app/tools/vault-export/export-web.component.ts new file mode 100644 index 0000000000..f2612656ce --- /dev/null +++ b/apps/web/src/app/tools/vault-export/export-web.component.ts @@ -0,0 +1,24 @@ +import { Component } from "@angular/core"; +import { Router } from "@angular/router"; + +import { ExportComponent } from "@bitwarden/vault-export-ui"; + +import { HeaderModule } from "../../layouts/header/header.module"; +import { SharedModule } from "../../shared"; + +@Component({ + templateUrl: "export-web.component.html", + standalone: true, + imports: [SharedModule, ExportComponent, HeaderModule], +}) +export class ExportWebComponent { + protected loading = false; + protected disabled = false; + + constructor(private router: Router) {} + + /** + * Callback that is called after a successful export. + */ + protected async onSuccessfulExport(organizationId: string): Promise {} +} diff --git a/apps/web/src/app/tools/vault-export/export.component.html b/apps/web/src/app/tools/vault-export/export.component.html deleted file mode 100644 index 9f47adf8aa..0000000000 --- a/apps/web/src/app/tools/vault-export/export.component.html +++ /dev/null @@ -1,115 +0,0 @@ - - - -
- - {{ "personalVaultExportPolicyInEffect" | i18n }} - - - - - - {{ "exportFrom" | i18n }} - - - - - - - - - {{ "fileFormat" | i18n }} - - - - - - - - {{ "exportTypeHeading" | i18n }} - - - {{ "accountRestricted" | i18n }} - {{ "accountRestrictedOptionDescription" | i18n }} - - - - {{ "passwordProtected" | i18n }} - {{ "passwordProtectedOptionDescription" | i18n }} - - - - -
- - {{ "filePassword" | i18n }} - - - {{ "exportPasswordDescription" | i18n }} - - - -
- - {{ "confirmFilePassword" | i18n }} - - - -
-
- - -
-
diff --git a/apps/web/src/app/tools/vault-export/export.component.ts b/apps/web/src/app/tools/vault-export/export.component.ts deleted file mode 100644 index 8b5f82167d..0000000000 --- a/apps/web/src/app/tools/vault-export/export.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Component } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; - -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 { 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"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; -import { ExportComponent as BaseExportComponent } from "@bitwarden/vault-export-ui"; - -@Component({ - selector: "app-export", - templateUrl: "export.component.html", -}) -export class ExportComponent extends BaseExportComponent { - constructor( - i18nService: I18nService, - toastService: ToastService, - exportService: VaultExportServiceAbstraction, - eventCollectionService: EventCollectionService, - policyService: PolicyService, - logService: LogService, - formBuilder: UntypedFormBuilder, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - organizationService: OrganizationService, - ) { - super( - i18nService, - toastService, - exportService, - eventCollectionService, - policyService, - logService, - formBuilder, - fileDownloadService, - dialogService, - organizationService, - ); - } - - protected saved() { - super.saved(); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("exportSuccess"), - }); - } -} diff --git a/apps/web/src/app/tools/vault-export/export.module.ts b/apps/web/src/app/tools/vault-export/export.module.ts deleted file mode 100644 index ddf82b0a10..0000000000 --- a/apps/web/src/app/tools/vault-export/export.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui"; - -import { LooseComponentsModule, SharedModule } from "../../shared"; - -import { ExportRoutingModule } from "./export-routing.module"; -import { ExportComponent } from "./export.component"; - -@NgModule({ - imports: [SharedModule, LooseComponentsModule, ExportRoutingModule, ExportScopeCalloutComponent], - declarations: [ExportComponent], -}) -export class ExportModule {} diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts index 9f81f5e550..baa463d913 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts @@ -1,5 +1,13 @@ import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from "@angular/core"; +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; import { ReactiveFormsModule, UntypedFormBuilder, Validators } from "@angular/forms"; import { map, merge, Observable, startWith, Subject, takeUntil } from "rxjs"; @@ -53,6 +61,26 @@ import { ExportScopeCalloutComponent } from "./export-scope-callout.component"; ], }) export class ExportComponent implements OnInit, OnDestroy { + private _organizationId: string; + + get organizationId(): string { + return this._organizationId; + } + + /** + * Enables the hosting control to pass in an organizationId + * If a organizationId is provided, the organization selection is disabled. + */ + @Input() set organizationId(value: string) { + this._organizationId = value; + this.organizationService + .get$(this._organizationId) + .pipe(takeUntil(this.destroy$)) + .subscribe((organization) => { + this._organizationId = organization?.id; + }); + } + /** * The hosting control also needs a bitSubmitDirective (on the Submit button) which calls this components {@link submit}-method. * This components formState (loading/disabled) is emitted back up to the hosting component so for example the Submit button can be enabled/disabled and show loading state. @@ -82,7 +110,6 @@ export class ExportComponent implements OnInit, OnDestroy { @Output() onSuccessfulExport = new EventEmitter(); - @Output() onSaved = new EventEmitter(); @ViewChild(PasswordStrengthComponent) passwordStrengthComponent: PasswordStrengthComponent; encryptedExportType = EncryptedExportType; @@ -91,7 +118,6 @@ export class ExportComponent implements OnInit, OnDestroy { filePasswordValue: string = null; private _disabledByPolicy = false; - protected organizationId: string = null; organizations$: Observable; protected get disabledByPolicy(): boolean { @@ -120,6 +146,7 @@ export class ExportComponent implements OnInit, OnDestroy { ]; private destroy$ = new Subject(); + private onlyManagedCollections = true; constructor( protected i18nService: I18nService, @@ -163,6 +190,8 @@ export class ExportComponent implements OnInit, OnDestroy { ); this.exportForm.controls.vaultSelector.patchValue(this.organizationId); this.exportForm.controls.vaultSelector.disable(); + + this.onlyManagedCollections = false; return; } @@ -211,7 +240,12 @@ export class ExportComponent implements OnInit, OnDestroy { try { const data = await this.getExportData(); this.downloadFile(data); - this.saved(); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("exportSuccess"), + }); + this.onSuccessfulExport.emit(this.organizationId); await this.collectEvent(); this.exportForm.get("secret").setValue(""); this.exportForm.clearValidators(); @@ -252,11 +286,6 @@ export class ExportComponent implements OnInit, OnDestroy { await this.doExport(); }; - protected saved() { - this.onSaved.emit(); - this.onSuccessfulExport.emit(this.organizationId); - } - private async verifyUser(): Promise { let confirmDescription = "exportWarningDesc"; if (this.isFileEncryptedExport) { @@ -298,7 +327,7 @@ export class ExportComponent implements OnInit, OnDestroy { this.organizationId, this.format, this.filePassword, - true, + this.onlyManagedCollections, ); }