PM-3231 Vault Onboarding Part 1 (#6905)
* Onboarding Component moved to web for sharing. Vault Onboarding Component created for new users. Still behind feature flag.
This commit is contained in:
parent
373a865a76
commit
fd8c26601a
|
@ -12,7 +12,7 @@
|
|||
<a bitLink *ngIf="route" [routerLink]="route">
|
||||
<ng-container *ngTemplateOutlet="content"></ng-container>
|
||||
</a>
|
||||
<button type="button" bitLink *ngIf="!route">
|
||||
<button type="button" bitLink *ngIf="!route" [disabled]="isDisabled">
|
||||
<ng-container *ngTemplateOutlet="content"></ng-container>
|
||||
</button>
|
||||
<div
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, Input } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "sm-onboarding-task",
|
||||
selector: "app-onboarding-task",
|
||||
templateUrl: "./onboarding-task.component.html",
|
||||
host: {
|
||||
class: "tw-max-w-max",
|
||||
|
@ -20,6 +20,9 @@ export class OnboardingTaskComponent {
|
|||
@Input()
|
||||
route: string | any[];
|
||||
|
||||
@Input()
|
||||
isDisabled: boolean = false;
|
||||
|
||||
handleClick(ev: MouseEvent) {
|
||||
/**
|
||||
* If the main `ng-content` is clicked, we don't want to trigger the task's click handler.
|
|
@ -3,7 +3,7 @@ import { Component, ContentChildren, EventEmitter, Input, Output, QueryList } fr
|
|||
import { OnboardingTaskComponent } from "./onboarding-task.component";
|
||||
|
||||
@Component({
|
||||
selector: "sm-onboarding",
|
||||
selector: "app-onboarding",
|
||||
templateUrl: "./onboarding.component.html",
|
||||
})
|
||||
export class OnboardingComponent {
|
|
@ -1,7 +1,8 @@
|
|||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { ProgressModule } from "@bitwarden/components";
|
||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||
|
||||
import { SharedModule } from "../../shared.module";
|
||||
|
||||
import { OnboardingTaskComponent } from "./onboarding-task.component";
|
||||
import { OnboardingComponent } from "./onboarding.component";
|
|
@ -5,7 +5,8 @@ import { delay, of, startWith } from "rxjs";
|
|||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { LinkModule, IconModule, ProgressModule } from "@bitwarden/components";
|
||||
import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/app/core/tests";
|
||||
|
||||
import { PreloadedEnglishI18nModule } from "../../../core/tests";
|
||||
|
||||
import { OnboardingTaskComponent } from "./onboarding-task.component";
|
||||
import { OnboardingComponent } from "./onboarding.component";
|
||||
|
@ -36,8 +37,8 @@ const Template: Story = (args) => ({
|
|||
...args,
|
||||
},
|
||||
template: `
|
||||
<sm-onboarding title="Get started">
|
||||
<sm-onboarding-task
|
||||
<app-onboarding title="Get started">
|
||||
<app-onboarding-task
|
||||
[title]="'createServiceAccount' | i18n"
|
||||
icon="bwi-cli"
|
||||
[completed]="createServiceAccount"
|
||||
|
@ -45,23 +46,23 @@ const Template: Story = (args) => ({
|
|||
<span>
|
||||
{{ "downloadThe" | i18n }} <a bitLink routerLink="">{{ "smCLI" | i18n }}</a>
|
||||
</span>
|
||||
</sm-onboarding-task>
|
||||
<sm-onboarding-task
|
||||
</app-onboarding-task>
|
||||
<app-onboarding-task
|
||||
[title]="'createProject' | i18n"
|
||||
icon="bwi-collection"
|
||||
[completed]="createProject"
|
||||
></sm-onboarding-task>
|
||||
<sm-onboarding-task
|
||||
></app-onboarding-task>
|
||||
<app-onboarding-task
|
||||
[title]="'importSecrets' | i18n"
|
||||
icon="bwi-download"
|
||||
[completed]="importSecrets$ | async"
|
||||
></sm-onboarding-task>
|
||||
<sm-onboarding-task
|
||||
></app-onboarding-task>
|
||||
<app-onboarding-task
|
||||
[title]="'createSecret' | i18n"
|
||||
icon="bwi-key"
|
||||
[completed]="createSecret"
|
||||
></sm-onboarding-task>
|
||||
</sm-onboarding>
|
||||
></app-onboarding-task>
|
||||
</app-onboarding>
|
||||
`,
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { Observable } from "rxjs";
|
||||
|
||||
import { VaultOnboardingTasks } from "../vault-onboarding.service";
|
||||
|
||||
export abstract class VaultOnboardingService {
|
||||
vaultOnboardingState$: Observable<VaultOnboardingTasks>;
|
||||
abstract setVaultOnboardingTasks(newState: VaultOnboardingTasks): Promise<void>;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import {
|
||||
ActiveUserState,
|
||||
KeyDefinition,
|
||||
StateProvider,
|
||||
VAULT_ONBOARDING,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
|
||||
import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./abstraction/vault-onboarding.service";
|
||||
|
||||
export type VaultOnboardingTasks = {
|
||||
createAccount: boolean;
|
||||
importData: boolean;
|
||||
installExtension: boolean;
|
||||
};
|
||||
|
||||
const VAULT_ONBOARDING_KEY = new KeyDefinition<VaultOnboardingTasks>(VAULT_ONBOARDING, "tasks", {
|
||||
deserializer: (jsonData) => jsonData,
|
||||
});
|
||||
|
||||
@Injectable()
|
||||
export class VaultOnboardingService implements VaultOnboardingServiceAbstraction {
|
||||
private vaultOnboardingState: ActiveUserState<VaultOnboardingTasks>;
|
||||
vaultOnboardingState$: Observable<VaultOnboardingTasks>;
|
||||
|
||||
constructor(private stateProvider: StateProvider) {
|
||||
this.vaultOnboardingState = this.stateProvider.getActive(VAULT_ONBOARDING_KEY);
|
||||
this.vaultOnboardingState$ = this.vaultOnboardingState.state$;
|
||||
}
|
||||
|
||||
async setVaultOnboardingTasks(newState: VaultOnboardingTasks): Promise<void> {
|
||||
await this.vaultOnboardingState.update(() => {
|
||||
return { ...newState };
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<div
|
||||
*ngIf="
|
||||
isNewAccount && showOnboarding && (showOnboardingAccess$ | async) && onboardingTasks$
|
||||
| async as onboardingTasks
|
||||
"
|
||||
class="tw-mb-6"
|
||||
>
|
||||
<app-onboarding
|
||||
*ngIf="onboardingTasks"
|
||||
[title]="'getStarted' | i18n"
|
||||
(dismiss)="hideOnboarding()"
|
||||
>
|
||||
<app-onboarding-task
|
||||
[title]="'createAnAccount' | i18n"
|
||||
[completed]="onboardingTasks.createAccount"
|
||||
[isDisabled]="true"
|
||||
></app-onboarding-task>
|
||||
|
||||
<app-onboarding-task
|
||||
[title]="'importData' | i18n"
|
||||
icon="bwi-save"
|
||||
[route]="['/tools/import']"
|
||||
[completed]="onboardingTasks.importData"
|
||||
>
|
||||
<p class="tw-pl-1">
|
||||
{{ "onboardingImportDataDetailsPartOne" | i18n }}
|
||||
<button type="button" bitLink (click)="emitToAddCipher()">
|
||||
{{ "onboardingImportDataDetailsLink" | i18n }}
|
||||
</button>
|
||||
{{ "onboardingImportDataDetailsPartTwo" | i18n }}
|
||||
</p>
|
||||
</app-onboarding-task>
|
||||
|
||||
<app-onboarding-task
|
||||
[title]="'installBrowserExtension' | i18n"
|
||||
icon="bwi-cli"
|
||||
(click)="navigateToExtension()"
|
||||
>
|
||||
<span class="tw-pl-1">
|
||||
{{ "installBrowserExtensionDetails" | i18n }}
|
||||
</span>
|
||||
</app-onboarding-task>
|
||||
</app-onboarding>
|
||||
</div>
|
|
@ -0,0 +1,146 @@
|
|||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
|
||||
import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./services/abstraction/vault-onboarding.service";
|
||||
import { VaultOnboardingComponent } from "./vault-onboarding.component";
|
||||
|
||||
describe("VaultOnboardingComponent", () => {
|
||||
let component: VaultOnboardingComponent;
|
||||
let fixture: ComponentFixture<VaultOnboardingComponent>;
|
||||
let mockPlatformUtilsService: Partial<PlatformUtilsService>;
|
||||
let mockApiService: Partial<ApiService>;
|
||||
let mockPolicyService: MockProxy<PolicyService>;
|
||||
let mockI18nService: MockProxy<I18nService>;
|
||||
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
||||
let mockVaultOnboardingService: MockProxy<VaultOnboardingServiceAbstraction>;
|
||||
let mockStateProvider: Partial<StateProvider>;
|
||||
let setInstallExtLinkSpy: any;
|
||||
let individualVaultPolicyCheckSpy: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPolicyService = mock<PolicyService>();
|
||||
mockI18nService = mock<I18nService>();
|
||||
mockPlatformUtilsService = mock<PlatformUtilsService>();
|
||||
mockApiService = {
|
||||
getProfile: jest.fn(),
|
||||
};
|
||||
mockConfigService = mock<ConfigServiceAbstraction>();
|
||||
mockVaultOnboardingService = mock<VaultOnboardingServiceAbstraction>();
|
||||
mockStateProvider = {
|
||||
getActive: jest.fn().mockReturnValue(
|
||||
of({
|
||||
vaultTasks: {
|
||||
createAccount: true,
|
||||
importData: false,
|
||||
installExtension: false,
|
||||
},
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [],
|
||||
imports: [RouterTestingModule],
|
||||
providers: [
|
||||
{ provide: PlatformUtilsService, useValue: mockPlatformUtilsService },
|
||||
{ provide: PolicyService, useValue: mockPolicyService },
|
||||
{ provide: VaultOnboardingServiceAbstraction, useValue: mockVaultOnboardingService },
|
||||
{ provide: I18nService, useValue: mockI18nService },
|
||||
{ provide: ApiService, useValue: mockApiService },
|
||||
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
|
||||
{ provide: StateProvider, useValue: mockStateProvider },
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(VaultOnboardingComponent);
|
||||
component = fixture.componentInstance;
|
||||
setInstallExtLinkSpy = jest.spyOn(component, "setInstallExtLink");
|
||||
individualVaultPolicyCheckSpy = jest
|
||||
.spyOn(component, "individualVaultPolicyCheck")
|
||||
.mockReturnValue(undefined);
|
||||
jest.spyOn(component, "checkCreationDate").mockReturnValue(null);
|
||||
(component as any).vaultOnboardingService.vaultOnboardingState$ = of({
|
||||
createAccount: true,
|
||||
importData: false,
|
||||
installExtension: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("ngOnInit", () => {
|
||||
it("should call setInstallExtLink", async () => {
|
||||
await component.ngOnInit();
|
||||
expect(setInstallExtLinkSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call individualVaultPolicyCheck", async () => {
|
||||
await component.ngOnInit();
|
||||
expect(individualVaultPolicyCheckSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("show and hide onboarding component", () => {
|
||||
it("should set showOnboarding to true", async () => {
|
||||
await component.ngOnInit();
|
||||
expect((component as any).showOnboarding).toBe(true);
|
||||
});
|
||||
|
||||
it("should set showOnboarding to false if dismiss is clicked", async () => {
|
||||
await component.ngOnInit();
|
||||
(component as any).hideOnboarding();
|
||||
expect((component as any).showOnboarding).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setInstallExtLink", () => {
|
||||
it("should set extensionUrl to Chrome Web Store when isChrome is true", async () => {
|
||||
jest.spyOn((component as any).platformUtilsService, "isChrome").mockReturnValue(true);
|
||||
const expected =
|
||||
"https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb";
|
||||
await component.ngOnInit();
|
||||
expect(component.extensionUrl).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should set extensionUrl to Firefox Store when isFirefox is true", async () => {
|
||||
jest.spyOn((component as any).platformUtilsService, "isFirefox").mockReturnValue(true);
|
||||
const expected = "https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/";
|
||||
await component.ngOnInit();
|
||||
expect(component.extensionUrl).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should set extensionUrl when isSafari is true", async () => {
|
||||
jest.spyOn((component as any).platformUtilsService, "isSafari").mockReturnValue(true);
|
||||
const expected = "https://apps.apple.com/us/app/bitwarden/id1352778147?mt=12";
|
||||
await component.ngOnInit();
|
||||
expect(component.extensionUrl).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("individualVaultPolicyCheck", () => {
|
||||
it("should set isIndividualPolicyVault to true", async () => {
|
||||
individualVaultPolicyCheckSpy.mockRestore();
|
||||
const spy = jest
|
||||
.spyOn((component as any).policyService, "policyAppliesToActiveUser$")
|
||||
.mockReturnValue(of(true));
|
||||
|
||||
await component.individualVaultPolicyCheck();
|
||||
fixture.detectChanges();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,165 @@
|
|||
import { CommonModule } from "@angular/common";
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
OnDestroy,
|
||||
SimpleChanges,
|
||||
OnChanges,
|
||||
} from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject, takeUntil, Observable, firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { LinkModule } from "@bitwarden/components";
|
||||
|
||||
import { OnboardingModule } from "../../../shared/components/onboarding/onboarding.module";
|
||||
|
||||
import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./services/abstraction/vault-onboarding.service";
|
||||
import { VaultOnboardingTasks } from "./services/vault-onboarding.service";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [OnboardingModule, CommonModule, JslibModule, LinkModule],
|
||||
selector: "app-vault-onboarding",
|
||||
templateUrl: "vault-onboarding.component.html",
|
||||
})
|
||||
export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() ciphers: CipherView[];
|
||||
@Output() onAddCipher = new EventEmitter<void>();
|
||||
|
||||
extensionUrl: string;
|
||||
isIndividualPolicyVault: boolean;
|
||||
private destroy$ = new Subject<void>();
|
||||
isNewAccount: boolean;
|
||||
private readonly onboardingReleaseDate = new Date("2024-01-01");
|
||||
showOnboardingAccess$: Observable<boolean>;
|
||||
|
||||
protected currentTasks: VaultOnboardingTasks;
|
||||
|
||||
protected onboardingTasks$: Observable<VaultOnboardingTasks>;
|
||||
protected showOnboarding = false;
|
||||
|
||||
constructor(
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected policyService: PolicyService,
|
||||
protected router: Router,
|
||||
private apiService: ApiService,
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private vaultOnboardingService: VaultOnboardingServiceAbstraction,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.showOnboardingAccess$ = await this.configService.getFeatureFlag$<boolean>(
|
||||
FeatureFlag.VaultOnboarding,
|
||||
false,
|
||||
);
|
||||
this.onboardingTasks$ = this.vaultOnboardingService.vaultOnboardingState$;
|
||||
await this.setOnboardingTasks();
|
||||
this.setInstallExtLink();
|
||||
this.individualVaultPolicyCheck();
|
||||
}
|
||||
|
||||
async ngOnChanges(changes: SimpleChanges) {
|
||||
if (this.showOnboarding && changes?.ciphers) {
|
||||
await this.saveCompletedTasks({
|
||||
createAccount: true,
|
||||
importData: this.ciphers.length > 0,
|
||||
installExtension: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async checkCreationDate() {
|
||||
const userProfile = await this.apiService.getProfile();
|
||||
const profileCreationDate = new Date(userProfile.creationDate);
|
||||
|
||||
this.isNewAccount = this.onboardingReleaseDate < profileCreationDate ? true : false;
|
||||
|
||||
if (!this.isNewAccount) {
|
||||
await this.hideOnboarding();
|
||||
}
|
||||
}
|
||||
|
||||
protected async hideOnboarding() {
|
||||
await this.saveCompletedTasks({
|
||||
createAccount: true,
|
||||
importData: true,
|
||||
installExtension: true,
|
||||
});
|
||||
}
|
||||
|
||||
async setOnboardingTasks() {
|
||||
const currentTasks = await firstValueFrom(this.onboardingTasks$);
|
||||
if (currentTasks == null) {
|
||||
const freshStart = {
|
||||
createAccount: true,
|
||||
importData: this.ciphers?.length > 0,
|
||||
installExtension: false,
|
||||
};
|
||||
await this.saveCompletedTasks(freshStart);
|
||||
} else if (currentTasks) {
|
||||
this.showOnboarding = Object.values(currentTasks).includes(false);
|
||||
}
|
||||
|
||||
if (this.showOnboarding) {
|
||||
await this.checkCreationDate();
|
||||
}
|
||||
}
|
||||
|
||||
private async saveCompletedTasks(vaultTasks: VaultOnboardingTasks) {
|
||||
this.showOnboarding = Object.values(vaultTasks).includes(false);
|
||||
await this.vaultOnboardingService.setVaultOnboardingTasks(vaultTasks);
|
||||
}
|
||||
|
||||
individualVaultPolicyCheck() {
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((data) => {
|
||||
this.isIndividualPolicyVault = data;
|
||||
});
|
||||
}
|
||||
|
||||
emitToAddCipher() {
|
||||
this.onAddCipher.emit();
|
||||
}
|
||||
|
||||
setInstallExtLink() {
|
||||
if (this.platformUtilsService.isChrome()) {
|
||||
this.extensionUrl =
|
||||
"https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb";
|
||||
} else if (this.platformUtilsService.isFirefox()) {
|
||||
this.extensionUrl =
|
||||
"https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/";
|
||||
} else if (this.platformUtilsService.isSafari()) {
|
||||
this.extensionUrl = "https://apps.apple.com/us/app/bitwarden/id1352778147?mt=12";
|
||||
} else if (this.platformUtilsService.isOpera()) {
|
||||
this.extensionUrl =
|
||||
"https://addons.opera.com/extensions/details/bitwarden-free-password-manager/";
|
||||
} else if (this.platformUtilsService.isEdge()) {
|
||||
this.extensionUrl =
|
||||
"https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh";
|
||||
} else {
|
||||
this.extensionUrl = "https://bitwarden.com/download/#downloads-web-browser";
|
||||
}
|
||||
}
|
||||
|
||||
navigateToExtension() {
|
||||
window.open(this.extensionUrl, "_blank");
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
<div class="container page-content">
|
||||
<app-vault-onboarding [ciphers]="ciphers" (onAddCipher)="addCipher()"> </app-vault-onboarding>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="groupings">
|
||||
|
|
|
@ -13,6 +13,9 @@ import { OrganizationBadgeModule } from "./organization-badge/organization-badge
|
|||
import { PipesModule } from "./pipes/pipes.module";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
import { VaultHeaderComponent } from "./vault-header/vault-header.component";
|
||||
import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./vault-onboarding/services/abstraction/vault-onboarding.service";
|
||||
import { VaultOnboardingService } from "./vault-onboarding/services/vault-onboarding.service";
|
||||
import { VaultOnboardingComponent } from "./vault-onboarding/vault-onboarding.component";
|
||||
import { VaultRoutingModule } from "./vault-routing.module";
|
||||
import { VaultComponent } from "./vault.component";
|
||||
|
||||
|
@ -30,8 +33,15 @@ import { VaultComponent } from "./vault.component";
|
|||
BreadcrumbsModule,
|
||||
VaultItemsModule,
|
||||
CollectionDialogModule,
|
||||
VaultOnboardingComponent,
|
||||
],
|
||||
declarations: [VaultComponent, VaultHeaderComponent],
|
||||
exports: [VaultComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: VaultOnboardingServiceAbstraction,
|
||||
useClass: VaultOnboardingService,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class VaultModule {}
|
||||
|
|
|
@ -1347,6 +1347,18 @@
|
|||
"importData": {
|
||||
"message": "Import data"
|
||||
},
|
||||
"onboardingImportDataDetailsPartOne": {
|
||||
"message": "If you don't have any data to import, you can create a ",
|
||||
"description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership."
|
||||
},
|
||||
"onboardingImportDataDetailsLink": {
|
||||
"message": "new item",
|
||||
"description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership."
|
||||
},
|
||||
"onboardingImportDataDetailsPartTwo": {
|
||||
"message": " instead. You may need to wait until your administrator confirms your organization membership.",
|
||||
"description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership."
|
||||
},
|
||||
"importError": {
|
||||
"message": "Import error"
|
||||
},
|
||||
|
@ -6912,6 +6924,9 @@
|
|||
"message": "SDK",
|
||||
"description": "Software Development Kit"
|
||||
},
|
||||
"createAnAccount": {
|
||||
"message": "Create an account"
|
||||
},
|
||||
"createSecret": {
|
||||
"message": "Create a secret"
|
||||
},
|
||||
|
@ -7456,6 +7471,12 @@
|
|||
"message": "See detailed instructions on our help site at",
|
||||
"description": "This is followed a by a hyperlink to the help website."
|
||||
},
|
||||
"installBrowserExtension": {
|
||||
"message": "Install browser extension"
|
||||
},
|
||||
"installBrowserExtensionDetails": {
|
||||
"message": "Use the extension to quickly save logins and auto-fill forms without opening the web app."
|
||||
},
|
||||
"projectAccessUpdated": {
|
||||
"message": "Project access updated"
|
||||
},
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
</app-header>
|
||||
|
||||
<div *ngIf="!loading && view$ | async as view; else spinner">
|
||||
<sm-onboarding [title]="'getStarted' | i18n" *ngIf="showOnboarding" (dismiss)="hideOnboarding()">
|
||||
<sm-onboarding-task
|
||||
<app-onboarding [title]="'getStarted' | i18n" *ngIf="showOnboarding" (dismiss)="hideOnboarding()">
|
||||
<app-onboarding-task
|
||||
[title]="'createServiceAccount' | i18n"
|
||||
(click)="openServiceAccountDialog()"
|
||||
icon="bwi-cli"
|
||||
|
@ -16,29 +16,29 @@
|
|||
"smCLI" | i18n
|
||||
}}</a>
|
||||
</span>
|
||||
</sm-onboarding-task>
|
||||
<sm-onboarding-task
|
||||
</app-onboarding-task>
|
||||
<app-onboarding-task
|
||||
*ngIf="userIsAdmin"
|
||||
[title]="'createProject' | i18n"
|
||||
(click)="openNewProjectDialog()"
|
||||
icon="bwi-collection"
|
||||
[completed]="view.tasks.createProject"
|
||||
></sm-onboarding-task>
|
||||
<sm-onboarding-task
|
||||
></app-onboarding-task>
|
||||
<app-onboarding-task
|
||||
*ngIf="userIsAdmin"
|
||||
[title]="'importSecrets' | i18n"
|
||||
[route]="['settings', 'import']"
|
||||
icon="bwi-download"
|
||||
[completed]="view.tasks.importSecrets"
|
||||
></sm-onboarding-task>
|
||||
<sm-onboarding-task
|
||||
></app-onboarding-task>
|
||||
<app-onboarding-task
|
||||
*ngIf="view.tasks.createProject"
|
||||
[title]="'createSecret' | i18n"
|
||||
(click)="openSecretDialog()"
|
||||
icon="bwi-key"
|
||||
[completed]="view.tasks.createSecret"
|
||||
></sm-onboarding-task>
|
||||
</sm-onboarding>
|
||||
></app-onboarding-task>
|
||||
</app-onboarding>
|
||||
|
||||
<div class="tw-mt-6 tw-flex tw-flex-col tw-gap-6">
|
||||
<sm-section>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { OnboardingModule } from "../../../../../../apps/web/src/app/shared/components/onboarding/onboarding.module";
|
||||
import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
|
||||
|
||||
import { OnboardingModule } from "./onboarding.module";
|
||||
import { OverviewRoutingModule } from "./overview-routing.module";
|
||||
import { OverviewComponent } from "./overview.component";
|
||||
import { SectionComponent } from "./section.component";
|
||||
|
|
|
@ -4,6 +4,7 @@ export enum FeatureFlag {
|
|||
ItemShare = "item-share",
|
||||
FlexibleCollectionsV1 = "flexible-collections-v-1", // v-1 is intentional
|
||||
BulkCollectionAccess = "bulk-collection-access",
|
||||
VaultOnboarding = "vault-onboarding",
|
||||
GeneratorToolsModernization = "generator-tools-modernization",
|
||||
KeyRotationImprovements = "key-rotation-improvements",
|
||||
FlexibleCollectionsMigration = "flexible-collections-migration",
|
||||
|
|
|
@ -16,6 +16,7 @@ export class ProfileResponse extends BaseResponse {
|
|||
twoFactorEnabled: boolean;
|
||||
key: string;
|
||||
avatarColor: string;
|
||||
creationDate: string;
|
||||
privateKey: string;
|
||||
securityStamp: string;
|
||||
forcePasswordReset: boolean;
|
||||
|
@ -37,6 +38,7 @@ export class ProfileResponse extends BaseResponse {
|
|||
this.twoFactorEnabled = this.getResponseProperty("TwoFactorEnabled");
|
||||
this.key = this.getResponseProperty("Key");
|
||||
this.avatarColor = this.getResponseProperty("AvatarColor");
|
||||
this.creationDate = this.getResponseProperty("CreationDate");
|
||||
this.privateKey = this.getResponseProperty("PrivateKey");
|
||||
this.securityStamp = this.getResponseProperty("SecurityStamp");
|
||||
this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset") ?? false;
|
||||
|
|
|
@ -27,6 +27,10 @@ export const SSO_DISK = new StateDefinition("ssoLogin", "disk");
|
|||
|
||||
export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk");
|
||||
|
||||
export const VAULT_ONBOARDING = new StateDefinition("vaultOnboarding", "disk", {
|
||||
web: "disk-local",
|
||||
});
|
||||
|
||||
export const GENERATOR_DISK = new StateDefinition("generator", "disk");
|
||||
export const GENERATOR_MEMORY = new StateDefinition("generator", "memory");
|
||||
|
||||
|
|
Loading…
Reference in New Issue