mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-24 12:06:15 +01:00
[PM-4222] Make importer UI reusable (#6504)
* 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 * Renamed filenames according to export rename * Make ImportWebComponent standalone, simplify routing * Pass organizationId as Input to ImportComponent * use formLoading and formDisabled outputs * 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 * fix table themes on desktop & browser * fix fileSelector button styles * update selectors to use tools prefix; remove unused 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 * Use SharedModule as import in import-web.component * File selector should be displayed as secondary * Use bitSubmit to override submit preventDefault (#6607) Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> Co-authored-by: William Martin <contact@willmartian.com>
This commit is contained in:
parent
d0e72f5554
commit
9e290a3fed
@ -113,7 +113,7 @@ import {
|
||||
ImportApiService,
|
||||
ImportServiceAbstraction,
|
||||
ImportService,
|
||||
} from "@bitwarden/importer";
|
||||
} from "@bitwarden/importer/core";
|
||||
|
||||
import { BrowserOrganizationService } from "../admin-console/services/browser-organization.service";
|
||||
import { BrowserPolicyService } from "../admin-console/services/browser-policy.service";
|
||||
|
@ -84,7 +84,7 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
||||
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export";
|
||||
import { ImportServiceAbstraction } from "@bitwarden/importer";
|
||||
import { ImportServiceAbstraction } from "@bitwarden/importer/core";
|
||||
|
||||
import { BrowserOrganizationService } from "../../admin-console/services/browser-organization.service";
|
||||
import { BrowserPolicyService } from "../../admin-console/services/browser-policy.service";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ImportApiService, ImportApiServiceAbstraction } from "@bitwarden/importer";
|
||||
import { ImportApiService, ImportApiServiceAbstraction } from "@bitwarden/importer/core";
|
||||
|
||||
import {
|
||||
ApiServiceInitOptions,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ImportService, ImportServiceAbstraction } from "@bitwarden/importer";
|
||||
import { ImportService, ImportServiceAbstraction } from "@bitwarden/importer/core";
|
||||
|
||||
import {
|
||||
cryptoServiceFactory,
|
||||
|
@ -15,7 +15,8 @@
|
||||
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
||||
"@bitwarden/components": ["../../libs/components/src"],
|
||||
"@bitwarden/exporter/*": ["../../libs/exporter/src/*"],
|
||||
"@bitwarden/importer": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer/core": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer/ui": ["../../libs/importer/src/components"],
|
||||
"@bitwarden/vault": ["../../libs/vault/src"]
|
||||
},
|
||||
"useDefineForClassFields": false
|
||||
|
@ -74,7 +74,7 @@ import {
|
||||
ImportApiServiceAbstraction,
|
||||
ImportService,
|
||||
ImportServiceAbstraction,
|
||||
} from "@bitwarden/importer";
|
||||
} from "@bitwarden/importer/core";
|
||||
import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service";
|
||||
|
||||
import { CliConfigService } from "./platform/services/cli-config.service";
|
||||
|
@ -3,7 +3,7 @@ import * as inquirer from "inquirer";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ImportServiceAbstraction, ImportType } from "@bitwarden/importer";
|
||||
import { ImportServiceAbstraction, ImportType } from "@bitwarden/importer/core";
|
||||
|
||||
import { Response } from "../models/response";
|
||||
import { MessageResponse } from "../models/response/message.response";
|
||||
|
@ -14,7 +14,7 @@
|
||||
"paths": {
|
||||
"@bitwarden/common/spec": ["../../libs/common/spec"],
|
||||
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
||||
"@bitwarden/importer": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer/core": ["../../libs/importer/src"],
|
||||
"@bitwarden/exporter/*": ["../../libs/exporter/src/*"],
|
||||
"@bitwarden/node/*": ["../../libs/node/src/*"]
|
||||
}
|
||||
|
@ -15,7 +15,8 @@
|
||||
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
||||
"@bitwarden/components": ["../../libs/components/src"],
|
||||
"@bitwarden/exporter/*": ["../../libs/exporter/src/*"],
|
||||
"@bitwarden/importer": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer/core": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer/ui": ["../../libs/importer/src/components"],
|
||||
"@bitwarden/vault": ["../../libs/vault/src"]
|
||||
},
|
||||
"useDefineForClassFields": false
|
||||
|
@ -48,8 +48,15 @@ const routes: Routes = [
|
||||
children: [
|
||||
{
|
||||
path: "import",
|
||||
loadChildren: () =>
|
||||
import("../tools/import/org-import.module").then((m) => m.OrganizationImportModule),
|
||||
loadComponent: () =>
|
||||
import("../../../tools/import/import-web.component").then(
|
||||
(mod) => mod.ImportWebComponent
|
||||
),
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "importData",
|
||||
organizationPermissions: (org: Organization) => org.canAccessImportExport,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "export",
|
||||
|
@ -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 { OrganizationImportComponent } from "./org-import.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: OrganizationImportComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "importData",
|
||||
organizationPermissions: (org: Organization) => org.canAccessImportExport,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
})
|
||||
export class OrganizationImportRoutingModule {}
|
@ -1,99 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { switchMap, takeUntil } from "rxjs/operators";
|
||||
|
||||
import {
|
||||
canAccessVaultTab,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { ImportServiceAbstraction } from "@bitwarden/importer";
|
||||
|
||||
import { ImportComponent } from "../../../../tools/import/import.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-import",
|
||||
templateUrl: "../../../../tools/import/import.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class OrganizationImportComponent extends ImportComponent {
|
||||
organization: Organization;
|
||||
|
||||
protected get importBlockedByPolicy(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
importService: ImportServiceAbstraction,
|
||||
router: Router,
|
||||
private route: ActivatedRoute,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
policyService: PolicyService,
|
||||
organizationService: OrganizationService,
|
||||
logService: LogService,
|
||||
syncService: SyncService,
|
||||
dialogService: DialogService,
|
||||
folderService: FolderService,
|
||||
collectionService: CollectionService,
|
||||
formBuilder: FormBuilder
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
importService,
|
||||
router,
|
||||
platformUtilsService,
|
||||
policyService,
|
||||
logService,
|
||||
syncService,
|
||||
dialogService,
|
||||
folderService,
|
||||
collectionService,
|
||||
organizationService,
|
||||
formBuilder
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.params
|
||||
.pipe(
|
||||
switchMap((params) => this.organizationService.get$(params.organizationId)),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe((organization) => {
|
||||
this.organizationId = organization.id;
|
||||
this.organization = organization;
|
||||
});
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
protected async onSuccessfulImport(): Promise<void> {
|
||||
if (canAccessVaultTab(this.organization)) {
|
||||
await this.router.navigate(["organizations", this.organizationId, "vault"]);
|
||||
} else {
|
||||
this.fileSelected = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected async performImport() {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "warning" },
|
||||
content: { key: "importWarning", placeholders: [this.organization.name] },
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
await super.performImport();
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import {
|
||||
ImportService,
|
||||
ImportServiceAbstraction,
|
||||
ImportApiService,
|
||||
ImportApiServiceAbstraction,
|
||||
} from "@bitwarden/importer";
|
||||
|
||||
import { LooseComponentsModule, SharedModule } from "../../../../shared";
|
||||
|
||||
import { OrganizationImportRoutingModule } from "./org-import-routing.module";
|
||||
import { OrganizationImportComponent } from "./org-import.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, LooseComponentsModule, OrganizationImportRoutingModule],
|
||||
declarations: [OrganizationImportComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: ImportApiServiceAbstraction,
|
||||
useClass: ImportApiService,
|
||||
deps: [ApiService],
|
||||
},
|
||||
{
|
||||
provide: ImportServiceAbstraction,
|
||||
useClass: ImportService,
|
||||
deps: [
|
||||
CipherService,
|
||||
FolderService,
|
||||
ImportApiServiceAbstraction,
|
||||
I18nService,
|
||||
CollectionService,
|
||||
CryptoService,
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class OrganizationImportModule {}
|
@ -255,7 +255,11 @@ const routes: Routes = [
|
||||
{ path: "", pathMatch: "full", redirectTo: "generator" },
|
||||
{
|
||||
path: "import",
|
||||
loadChildren: () => import("./tools/import/import.module").then((m) => m.ImportModule),
|
||||
loadComponent: () =>
|
||||
import("./tools/import/import-web.component").then((mod) => mod.ImportWebComponent),
|
||||
data: {
|
||||
titleId: "importData",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "export",
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component } from "@angular/core";
|
||||
import { FormControl, Validators } from "@angular/forms";
|
||||
|
||||
@Component({
|
||||
templateUrl: "file-password-prompt.component.html",
|
||||
})
|
||||
export class FilePasswordPromptComponent {
|
||||
filePassword = new FormControl("", Validators.required);
|
||||
|
||||
constructor(public dialogRef: DialogRef) {}
|
||||
|
||||
submit() {
|
||||
this.filePassword.markAsTouched();
|
||||
if (!this.filePassword.valid) {
|
||||
return;
|
||||
}
|
||||
this.dialogRef.close(this.filePassword.value);
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { ImportComponent } from "./import.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: ImportComponent,
|
||||
data: { titleId: "importData" },
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
})
|
||||
export class ImportRoutingModule {}
|
18
apps/web/src/app/tools/import/import-web.component.html
Normal file
18
apps/web/src/app/tools/import/import-web.component.html
Normal file
@ -0,0 +1,18 @@
|
||||
<h1 bitTypography="h1">{{ "importData" | i18n }}</h1>
|
||||
<tools-import
|
||||
(formDisabled)="this.disabled = $event"
|
||||
(formLoading)="this.loading = $event"
|
||||
(onSuccessfulImport)="this.onSuccessfulImport($event)"
|
||||
organizationId="{{ routeOrgId }}"
|
||||
></tools-import>
|
||||
<button
|
||||
[disabled]="disabled"
|
||||
[loading]="loading"
|
||||
form="importForm"
|
||||
bitButton
|
||||
type="submit"
|
||||
bitFormButton
|
||||
buttonType="primary"
|
||||
>
|
||||
{{ "importData" | i18n }}
|
||||
</button>
|
51
apps/web/src/app/tools/import/import-web.component.ts
Normal file
51
apps/web/src/app/tools/import/import-web.component.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import {
|
||||
OrganizationService,
|
||||
canAccessVaultTab,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ImportComponent } from "@bitwarden/importer/ui";
|
||||
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
@Component({
|
||||
templateUrl: "import-web.component.html",
|
||||
standalone: true,
|
||||
imports: [SharedModule, ImportComponent],
|
||||
})
|
||||
export class ImportWebComponent implements OnInit {
|
||||
protected routeOrgId: string = null;
|
||||
protected loading = false;
|
||||
protected disabled = false;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private organizationService: OrganizationService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.routeOrgId = this.route.snapshot.paramMap.get("organizationId");
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that is called after a successful import.
|
||||
*/
|
||||
protected async onSuccessfulImport(organizationId: string): Promise<void> {
|
||||
if (!organizationId) {
|
||||
await this.router.navigate(["vault"]);
|
||||
return;
|
||||
}
|
||||
|
||||
const organization = await firstValueFrom(this.organizationService.get$(organizationId));
|
||||
if (organization == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (canAccessVaultTab(organization)) {
|
||||
await this.router.navigate(["organizations", organizationId, "vault"]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import {
|
||||
ImportService,
|
||||
ImportServiceAbstraction,
|
||||
ImportApiService,
|
||||
ImportApiServiceAbstraction,
|
||||
} from "@bitwarden/importer";
|
||||
|
||||
import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||
|
||||
import {
|
||||
ImportErrorDialogComponent,
|
||||
ImportSuccessDialogComponent,
|
||||
FilePasswordPromptComponent,
|
||||
} from "./dialog";
|
||||
import { ImportRoutingModule } from "./import-routing.module";
|
||||
import { ImportComponent } from "./import.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, LooseComponentsModule, ImportRoutingModule],
|
||||
declarations: [
|
||||
ImportComponent,
|
||||
FilePasswordPromptComponent,
|
||||
ImportErrorDialogComponent,
|
||||
ImportSuccessDialogComponent,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ImportApiServiceAbstraction,
|
||||
useClass: ImportApiService,
|
||||
deps: [ApiService],
|
||||
},
|
||||
{
|
||||
provide: ImportServiceAbstraction,
|
||||
useClass: ImportService,
|
||||
deps: [
|
||||
CipherService,
|
||||
FolderService,
|
||||
ImportApiServiceAbstraction,
|
||||
I18nService,
|
||||
CollectionService,
|
||||
CryptoService,
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class ImportModule {}
|
@ -32,7 +32,7 @@ app-password-generator-history {
|
||||
}
|
||||
}
|
||||
|
||||
app-import {
|
||||
tools-import {
|
||||
textarea {
|
||||
height: 150px;
|
||||
}
|
||||
|
@ -10,7 +10,8 @@
|
||||
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
||||
"@bitwarden/components": ["../../libs/components/src"],
|
||||
"@bitwarden/exporter/*": ["../../libs/exporter/src/*"],
|
||||
"@bitwarden/importer": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer/core": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer/ui": ["../../libs/importer/src/components"],
|
||||
"@bitwarden/vault": ["../../libs/vault/src"],
|
||||
"@bitwarden/web-vault/*": ["src/*"]
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ import {
|
||||
ImportApiServiceAbstraction,
|
||||
ImportService,
|
||||
ImportServiceAbstraction,
|
||||
} from "@bitwarden/importer";
|
||||
} from "@bitwarden/importer/core";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from "./async-actions.module";
|
||||
export * from "./bit-action.directive";
|
||||
export * from "./form-button.directive";
|
||||
export * from "./bit-submit.directive";
|
||||
|
4
libs/components/src/table/table.component.css
Normal file
4
libs/components/src/table/table.component.css
Normal file
@ -0,0 +1,4 @@
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
@ -39,6 +39,8 @@ export class TableComponent implements OnDestroy, AfterContentChecked {
|
||||
"tw-w-full",
|
||||
"tw-leading-normal",
|
||||
"tw-text-main",
|
||||
"tw-border-collapse",
|
||||
"tw-text-start",
|
||||
this.layout === "auto" ? "tw-table-auto" : "tw-table-fixed",
|
||||
];
|
||||
}
|
||||
|
@ -159,6 +159,7 @@
|
||||
}
|
||||
|
||||
@import "./search/search.component.css";
|
||||
@import "./table/table.component.css";
|
||||
|
||||
/**
|
||||
* tw-break-words does not work with table cells:
|
||||
|
@ -1,4 +1,4 @@
|
||||
<form (submit)="submit()">
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog>
|
||||
<span bitDialogTitle>
|
||||
{{ "confirmVaultImport" | i18n }}
|
||||
@ -12,7 +12,7 @@
|
||||
bitInput
|
||||
type="password"
|
||||
name="filePassword"
|
||||
[formControl]="filePassword"
|
||||
formControlName="filePassword"
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
/>
|
@ -0,0 +1,43 @@
|
||||
import { DialogRef } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
templateUrl: "file-password-prompt.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
IconButtonModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
})
|
||||
export class FilePasswordPromptComponent {
|
||||
formGroup = this.formBuilder.group({
|
||||
filePassword: ["", Validators.required],
|
||||
});
|
||||
|
||||
constructor(public dialogRef: DialogRef, protected formBuilder: FormBuilder) {}
|
||||
|
||||
submit = () => {
|
||||
this.formGroup.markAsTouched();
|
||||
if (!this.formGroup.valid) {
|
||||
return;
|
||||
}
|
||||
this.dialogRef.close(this.formGroup.value.filePassword);
|
||||
};
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
|
||||
import { TableDataSource } from "@bitwarden/components";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ButtonModule, DialogModule, TableDataSource, TableModule } from "@bitwarden/components";
|
||||
|
||||
export interface ErrorListItem {
|
||||
type: string;
|
||||
@ -9,8 +11,9 @@ export interface ErrorListItem {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "app-import-error-dialog",
|
||||
templateUrl: "./import-error-dialog.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, JslibModule, DialogModule, TableModule, ButtonModule],
|
||||
})
|
||||
export class ImportErrorDialogComponent implements OnInit {
|
||||
protected dataSource = new TableDataSource<ErrorListItem>();
|
@ -1,9 +1,12 @@
|
||||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { TableDataSource } from "@bitwarden/components";
|
||||
import { ImportResult } from "@bitwarden/importer";
|
||||
import { ButtonModule, DialogModule, TableDataSource, TableModule } from "@bitwarden/components";
|
||||
|
||||
import { ImportResult } from "../../models";
|
||||
|
||||
export interface ResultList {
|
||||
icon: string;
|
||||
@ -13,6 +16,8 @@ export interface ResultList {
|
||||
|
||||
@Component({
|
||||
templateUrl: "./import-success-dialog.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, JslibModule, DialogModule, TableModule, ButtonModule],
|
||||
})
|
||||
export class ImportSuccessDialogComponent implements OnInit {
|
||||
protected dataSource = new TableDataSource<ResultList>();
|
@ -1,9 +1,7 @@
|
||||
<h1 bitTypography="h1">{{ "importData" | i18n }}</h1>
|
||||
|
||||
<bit-callout type="info" *ngIf="importBlockedByPolicy">
|
||||
{{ "personalOwnershipPolicyInEffectImports" | i18n }}
|
||||
</bit-callout>
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit" id="importForm">
|
||||
<bit-form-field>
|
||||
<bit-label
|
||||
>{{ "importDestination" | i18n }}
|
||||
@ -349,12 +347,7 @@
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "selectImportFile" | i18n }}</bit-label>
|
||||
<div class="file-selector">
|
||||
<button
|
||||
bitButton
|
||||
type="button"
|
||||
class="btn btn-outline-primary"
|
||||
(click)="fileSelector.click()"
|
||||
>
|
||||
<button bitButton type="button" buttonType="secondary" (click)="fileSelector.click()">
|
||||
{{ "chooseFile" | i18n }}
|
||||
</button>
|
||||
{{ this.fileSelected ? this.fileSelected.name : ("noFileChosen" | i18n) }}
|
||||
@ -380,13 +373,4 @@
|
||||
formControlName="fileContents"
|
||||
></textarea>
|
||||
</bit-form-field>
|
||||
<button
|
||||
bitButton
|
||||
bitFormButton
|
||||
type="submit"
|
||||
buttonType="primary"
|
||||
[disabled]="importBlockedByPolicy"
|
||||
>
|
||||
{{ "importData" | i18n }}
|
||||
</button>
|
||||
</form>
|
@ -1,10 +1,20 @@
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from "@angular/core";
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import * as JSZip from "jszip";
|
||||
import { concat, Observable, Subject, lastValueFrom, combineLatest } from "rxjs";
|
||||
import { map, takeUntil } from "rxjs/operators";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import {
|
||||
canAccessImportExport,
|
||||
OrganizationService,
|
||||
@ -12,22 +22,35 @@ import {
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import {
|
||||
ImportOption,
|
||||
ImportResult,
|
||||
AsyncActionsModule,
|
||||
BitSubmitDirective,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
DialogService,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
SelectModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { ImportOption, ImportResult, ImportType } from "../models";
|
||||
import {
|
||||
ImportApiService,
|
||||
ImportApiServiceAbstraction,
|
||||
ImportService,
|
||||
ImportServiceAbstraction,
|
||||
ImportType,
|
||||
} from "@bitwarden/importer";
|
||||
} from "../services";
|
||||
|
||||
import {
|
||||
FilePasswordPromptComponent,
|
||||
@ -36,8 +59,39 @@ import {
|
||||
} from "./dialog";
|
||||
|
||||
@Component({
|
||||
selector: "app-import",
|
||||
selector: "tools-import",
|
||||
templateUrl: "import.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
IconButtonModule,
|
||||
SelectModule,
|
||||
CalloutModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ImportApiServiceAbstraction,
|
||||
useClass: ImportApiService,
|
||||
deps: [ApiService],
|
||||
},
|
||||
{
|
||||
provide: ImportServiceAbstraction,
|
||||
useClass: ImportService,
|
||||
deps: [
|
||||
CipherService,
|
||||
FolderService,
|
||||
ImportApiServiceAbstraction,
|
||||
I18nService,
|
||||
CollectionService,
|
||||
CryptoService,
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class ImportComponent implements OnInit, OnDestroy {
|
||||
featuredImportOptions: ImportOption[];
|
||||
@ -49,7 +103,24 @@ export class ImportComponent implements OnInit, OnDestroy {
|
||||
collections$: Observable<CollectionView[]>;
|
||||
organizations$: Observable<Organization[]>;
|
||||
|
||||
protected organizationId: string = null;
|
||||
private _organizationId: string;
|
||||
|
||||
get organizationId(): string {
|
||||
return this._organizationId;
|
||||
}
|
||||
|
||||
@Input() set organizationId(value: string) {
|
||||
this._organizationId = value;
|
||||
this.organizationService
|
||||
.get$(this._organizationId)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((organization) => {
|
||||
this._organizationId = organization?.id;
|
||||
this.organization = organization;
|
||||
});
|
||||
}
|
||||
|
||||
protected organization: Organization;
|
||||
protected destroy$ = new Subject<void>();
|
||||
|
||||
private _importBlockedByPolicy = false;
|
||||
@ -68,10 +139,31 @@ export class ImportComponent implements OnInit, OnDestroy {
|
||||
file: [],
|
||||
});
|
||||
|
||||
@ViewChild(BitSubmitDirective)
|
||||
private bitSubmit: BitSubmitDirective;
|
||||
|
||||
@Output()
|
||||
formLoading = new EventEmitter<boolean>();
|
||||
|
||||
@Output()
|
||||
formDisabled = new EventEmitter<boolean>();
|
||||
|
||||
@Output()
|
||||
onSuccessfulImport = new EventEmitter<string>();
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.bitSubmit.loading$.pipe(takeUntil(this.destroy$)).subscribe((loading) => {
|
||||
this.formLoading.emit(loading);
|
||||
});
|
||||
|
||||
this.bitSubmit.disabled$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => {
|
||||
this.formDisabled.emit(disabled);
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected importService: ImportServiceAbstraction,
|
||||
protected router: Router,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected policyService: PolicyService,
|
||||
private logService: LogService,
|
||||
@ -87,13 +179,6 @@ export class ImportComponent implements OnInit, OnDestroy {
|
||||
return this._importBlockedByPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that is called after a successful import.
|
||||
*/
|
||||
protected async onSuccessfulImport(): Promise<void> {
|
||||
await this.router.navigate(["vault"]);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setImportOptions();
|
||||
|
||||
@ -167,6 +252,18 @@ export class ImportComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
|
||||
protected async performImport() {
|
||||
if (this.organization) {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "warning" },
|
||||
content: { key: "importWarning", placeholders: [this.organization.name] },
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.importBlockedByPolicy) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
@ -246,7 +343,7 @@ export class ImportComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
this.syncService.fullSync(true);
|
||||
await this.onSuccessfulImport();
|
||||
this.onSuccessfulImport.emit(this._organizationId);
|
||||
} catch (e) {
|
||||
this.dialogService.open<unknown, Error>(ImportErrorDialogComponent, {
|
||||
data: e,
|
3
libs/importer/src/components/index.ts
Normal file
3
libs/importer/src/components/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./dialog";
|
||||
|
||||
export { ImportComponent } from "./import.component";
|
@ -1,11 +1,5 @@
|
||||
export { ImportType, ImportOption } from "./models/import-options";
|
||||
export * from "./models";
|
||||
|
||||
export { ImportResult } from "./models/import-result";
|
||||
|
||||
export { ImportApiServiceAbstraction } from "./services/import-api.service.abstraction";
|
||||
export { ImportApiService } from "./services/import-api.service";
|
||||
|
||||
export { ImportServiceAbstraction } from "./services/import.service.abstraction";
|
||||
export { ImportService } from "./services/import.service";
|
||||
export * from "./services";
|
||||
|
||||
export { Importer } from "./importers/importer";
|
||||
|
2
libs/importer/src/models/index.ts
Normal file
2
libs/importer/src/models/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { ImportType, ImportOption } from "./import-options";
|
||||
export { ImportResult } from "./import-result";
|
5
libs/importer/src/services/index.ts
Normal file
5
libs/importer/src/services/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export { ImportApiServiceAbstraction } from "./import-api.service.abstraction";
|
||||
export { ImportApiService } from "./import-api.service";
|
||||
|
||||
export { ImportServiceAbstraction } from "./import.service.abstraction";
|
||||
export { ImportService } from "./import.service";
|
@ -7,7 +7,8 @@
|
||||
"@bitwarden/common/*": ["../common/src/*"],
|
||||
"@bitwarden/components": ["../components/src"],
|
||||
"@bitwarden/exporter/*": ["../exporter/src/*"],
|
||||
"@bitwarden/importer": ["../importer/src"],
|
||||
"@bitwarden/importer/core": ["../importer/src"],
|
||||
"@bitwarden/importer/ui": ["../importer/src/components"],
|
||||
"@bitwarden/node/*": ["../node/src/*"],
|
||||
"@bitwarden/vault": ["../vault/src"]
|
||||
}
|
||||
|
@ -20,7 +20,8 @@
|
||||
"@bitwarden/common/*": ["./libs/common/src/*"],
|
||||
"@bitwarden/components": ["./libs/components/src"],
|
||||
"@bitwarden/exporter/*": ["./libs/exporter/src/*"],
|
||||
"@bitwarden/importer": ["./libs/importer/src"],
|
||||
"@bitwarden/importer/core": ["./libs/importer/src"],
|
||||
"@bitwarden/importer/ui": ["./libs/importer/src/components"],
|
||||
"@bitwarden/node/*": ["./libs/node/src/*"],
|
||||
"@bitwarden/vault": ["./libs/vault/src"]
|
||||
},
|
||||
|
@ -20,7 +20,8 @@
|
||||
"@bitwarden/common/*": ["./libs/common/src/*"],
|
||||
"@bitwarden/components": ["./libs/components/src"],
|
||||
"@bitwarden/exporter/*": ["./libs/exporter/src/*"],
|
||||
"@bitwarden/importer": ["./libs/importer/src"],
|
||||
"@bitwarden/importer/core": ["./libs/importer/src"],
|
||||
"@bitwarden/importer/ui": ["./libs/importer/src/components"],
|
||||
"@bitwarden/node/*": ["./libs/node/src/*"],
|
||||
"@bitwarden/web-vault/*": ["./apps/web/src/*"],
|
||||
"@bitwarden/vault": ["./libs/vault/src"]
|
||||
|
Loading…
Reference in New Issue
Block a user