mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-22 11:45:59 +01:00
[SM-453] user onboarding component (#4707)
* wip onboarding component * fix button type * remove dismiss button * add completion logic * update styles; add download cli section; add click logic; add loading spinner * update i18n * update icons; rearrange items; fix import item logic * add complete i18n * fix reactivity * move visibility logic into presentational component * add button type * apply code reviews * add loading spinner to page * onboarding dismissal should persist when switching orgs * add workaround for inconsistent icon size * fix full storybook * apply code review; update stories
This commit is contained in:
parent
7ce4579214
commit
581f69256d
@ -6420,6 +6420,41 @@
|
|||||||
"errorReadingImportFile": {
|
"errorReadingImportFile": {
|
||||||
"message": "An error occurred when trying to read the import file"
|
"message": "An error occurred when trying to read the import file"
|
||||||
},
|
},
|
||||||
|
"createSecret": {
|
||||||
|
"message": "Create a secret"
|
||||||
|
},
|
||||||
|
"createProject": {
|
||||||
|
"message": "Create a project"
|
||||||
|
},
|
||||||
|
"createServiceAccount": {
|
||||||
|
"message": "Create a service account"
|
||||||
|
},
|
||||||
|
"downloadThe": {
|
||||||
|
"message": "Download the",
|
||||||
|
"description": "Link to a downloadable resource. This will be used as part of a larger phrase. Example: Download the Secrets Manager CLI"
|
||||||
|
},
|
||||||
|
"smCLI": {
|
||||||
|
"message": "Secrets Manager CLI"
|
||||||
|
},
|
||||||
|
"importSecrets": {
|
||||||
|
"message": "Import secrets"
|
||||||
|
},
|
||||||
|
"getStarted": {
|
||||||
|
"message": "Get started"
|
||||||
|
},
|
||||||
|
"complete": {
|
||||||
|
"message": "$COMPLETED$/$TOTAL$ Complete",
|
||||||
|
"placeholders": {
|
||||||
|
"COMPLETED": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "1"
|
||||||
|
},
|
||||||
|
"TOTAL": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"restoreSecret": {
|
"restoreSecret": {
|
||||||
"message": "Restore secret"
|
"message": "Restore secret"
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
<ng-template #content>
|
||||||
|
<i class="bwi bwi-fw !tw-mr-4" [ngClass]="completed ? 'bwi-check tw-text-success' : icon"></i
|
||||||
|
><span
|
||||||
|
[ngClass]="{
|
||||||
|
'tw-text-primary-700 tw-line-through tw-decoration-primary-700 tw-opacity-50': completed
|
||||||
|
}"
|
||||||
|
>{{ title }}<i class="bwi bwi-angle-right tw-ml-1"></i
|
||||||
|
></span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<li class="tw-list-none">
|
||||||
|
<a bitLink *ngIf="route" [routerLink]="route">
|
||||||
|
<ng-container *ngTemplateOutlet="content"></ng-container>
|
||||||
|
</a>
|
||||||
|
<button type="button" bitLink *ngIf="!route">
|
||||||
|
<ng-container *ngTemplateOutlet="content"></ng-container>
|
||||||
|
</button>
|
||||||
|
<div class="tw-ml-8 tw-mt-1 tw-text-sm" [ngClass]="{ 'tw-opacity-50': completed }">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
</li>
|
@ -0,0 +1,22 @@
|
|||||||
|
import { Component, Input } from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "sm-onboarding-task",
|
||||||
|
templateUrl: "./onboarding-task.component.html",
|
||||||
|
host: {
|
||||||
|
class: "tw-max-w-max",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class OnboardingTaskComponent {
|
||||||
|
@Input()
|
||||||
|
completed = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
icon = "bwi-info-circle";
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
route: string | any[];
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
<details
|
||||||
|
*ngIf="visible"
|
||||||
|
#details
|
||||||
|
class="tw-rounded-sm tw-bg-background-alt tw-text-main"
|
||||||
|
(toggle)="toggle()"
|
||||||
|
open
|
||||||
|
>
|
||||||
|
<summary class="tw-list-none tw-p-2 tw-px-4">
|
||||||
|
<div class="tw-flex tw-select-none tw-items-center tw-gap-4">
|
||||||
|
<i class="bwi bwi-dashboard tw-text-3xl tw-text-primary-500" aria-hidden="true"></i>
|
||||||
|
<div class="tw-text-lg">{{ title }}</div>
|
||||||
|
<bit-progress class="tw-flex-1" [showText]="false" [barWidth]="barWidth"></bit-progress>
|
||||||
|
<span *ngIf="tasks.length > 0; else spinner">
|
||||||
|
{{ "complete" | i18n: amountCompleted:tasks.length }}
|
||||||
|
</span>
|
||||||
|
<i
|
||||||
|
class="bwi tw-my-auto"
|
||||||
|
[ngClass]="open ? 'bwi-angle-down' : 'bwi-angle-up'"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
</summary>
|
||||||
|
<ul class="tw-ml-6 tw-mb-0 tw-flex tw-flex-col tw-gap-4">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</ul>
|
||||||
|
<div class="tw-p-4 tw-pt-0">
|
||||||
|
<button
|
||||||
|
bitLink
|
||||||
|
type="button"
|
||||||
|
class="tw-ml-auto tw-block"
|
||||||
|
[ngClass]="{ 'tw-invisible': !isComplete }"
|
||||||
|
(click)="dismiss()"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<ng-template #spinner>
|
||||||
|
<i class="bwi bwi-spinner bwi-spin"></i>
|
||||||
|
</ng-template>
|
@ -0,0 +1,39 @@
|
|||||||
|
import { AfterContentInit, Component, ContentChildren, Input, QueryList } from "@angular/core";
|
||||||
|
|
||||||
|
import { OnboardingTaskComponent } from "./onboarding-task.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "sm-onboarding",
|
||||||
|
templateUrl: "./onboarding.component.html",
|
||||||
|
})
|
||||||
|
export class OnboardingComponent implements AfterContentInit {
|
||||||
|
@ContentChildren(OnboardingTaskComponent) tasks: QueryList<OnboardingTaskComponent>;
|
||||||
|
@Input() title: string;
|
||||||
|
|
||||||
|
protected open = true;
|
||||||
|
protected visible = false;
|
||||||
|
|
||||||
|
ngAfterContentInit() {
|
||||||
|
this.visible = !this.isComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get amountCompleted(): number {
|
||||||
|
return this.tasks.filter((task) => task.completed).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get barWidth(): number {
|
||||||
|
return this.tasks.length === 0 ? 0 : (this.amountCompleted / this.tasks.length) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get isComplete(): boolean {
|
||||||
|
return this.tasks.length > 0 && this.tasks.length === this.amountCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toggle() {
|
||||||
|
this.open = !this.open;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected dismiss() {
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
|
import { ProgressModule } from "@bitwarden/components";
|
||||||
|
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||||
|
|
||||||
|
import { OnboardingTaskComponent } from "./onboarding-task.component";
|
||||||
|
import { OnboardingComponent } from "./onboarding.component";
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [SharedModule, ProgressModule],
|
||||||
|
exports: [OnboardingComponent, OnboardingTaskComponent],
|
||||||
|
declarations: [OnboardingComponent, OnboardingTaskComponent],
|
||||||
|
})
|
||||||
|
export class OnboardingModule {}
|
@ -0,0 +1,92 @@
|
|||||||
|
import { RouterModule } from "@angular/router";
|
||||||
|
import { Meta, Story, moduleMetadata } from "@storybook/angular";
|
||||||
|
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/tests/preloaded-english-i18n.module";
|
||||||
|
|
||||||
|
import { OnboardingTaskComponent } from "./onboarding-task.component";
|
||||||
|
import { OnboardingComponent } from "./onboarding.component";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Web/Onboarding",
|
||||||
|
component: OnboardingComponent,
|
||||||
|
decorators: [
|
||||||
|
moduleMetadata({
|
||||||
|
imports: [
|
||||||
|
JslibModule,
|
||||||
|
RouterModule.forRoot(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: OnboardingComponent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ useHash: true }
|
||||||
|
),
|
||||||
|
LinkModule,
|
||||||
|
IconModule,
|
||||||
|
ProgressModule,
|
||||||
|
PreloadedEnglishI18nModule,
|
||||||
|
],
|
||||||
|
declarations: [OnboardingTaskComponent],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
const Template: Story = (args) => ({
|
||||||
|
props: {
|
||||||
|
createServiceAccount: false,
|
||||||
|
importSecrets$: of(false),
|
||||||
|
createSecret: false,
|
||||||
|
createProject: false,
|
||||||
|
...args,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<sm-onboarding title="Get started">
|
||||||
|
<sm-onboarding-task
|
||||||
|
[title]="'createServiceAccount' | i18n"
|
||||||
|
icon="bwi-cli"
|
||||||
|
[completed]="createServiceAccount"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ "downloadThe" | i18n }} <a bitLink routerLink="">{{ "smCLI" | i18n }}</a>
|
||||||
|
</span>
|
||||||
|
</sm-onboarding-task>
|
||||||
|
<sm-onboarding-task
|
||||||
|
[title]="'importSecrets' | i18n"
|
||||||
|
icon="bwi-download"
|
||||||
|
[completed]="importSecrets$ | async"
|
||||||
|
></sm-onboarding-task>
|
||||||
|
<sm-onboarding-task
|
||||||
|
[title]="'createSecret' | i18n"
|
||||||
|
icon="bwi-key"
|
||||||
|
[completed]="createSecret"
|
||||||
|
></sm-onboarding-task>
|
||||||
|
<sm-onboarding-task
|
||||||
|
[title]="'createProject' | i18n"
|
||||||
|
icon="bwi-collection"
|
||||||
|
[completed]="createProject"
|
||||||
|
></sm-onboarding-task>
|
||||||
|
</sm-onboarding>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Empty = Template.bind({});
|
||||||
|
|
||||||
|
export const Partial = Template.bind({});
|
||||||
|
Partial.args = {
|
||||||
|
...Template.args,
|
||||||
|
createServiceAccount: true,
|
||||||
|
createProject: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Full = Template.bind({});
|
||||||
|
Full.args = {
|
||||||
|
...Template.args,
|
||||||
|
createServiceAccount: true,
|
||||||
|
createProject: true,
|
||||||
|
createSecret: true,
|
||||||
|
importSecrets$: of(true).pipe(delay(0), startWith(false)),
|
||||||
|
};
|
@ -1,37 +1,68 @@
|
|||||||
<sm-header [title]="organizationName">
|
<sm-header [title]="organizationName">
|
||||||
<sm-new-menu></sm-new-menu>
|
<sm-new-menu></sm-new-menu>
|
||||||
</sm-header>
|
</sm-header>
|
||||||
<div
|
|
||||||
*ngIf="view$ | async as view; else spinner"
|
<div *ngIf="view$ | async as view; else spinner">
|
||||||
class="tw-flex tw-flex-col tw-gap-6 [&_sm-header]:tw-hidden"
|
<sm-onboarding [title]="'getStarted' | i18n">
|
||||||
>
|
<sm-onboarding-task
|
||||||
<sm-section>
|
[title]="'createServiceAccount' | i18n"
|
||||||
<h2 slot="summary" class="tw-mb-0 tw-text-2xl tw-font-semibold">{{ "projects" | i18n }}</h2>
|
(click)="openServiceAccountDialog()"
|
||||||
<sm-projects-list
|
icon="bwi-cli"
|
||||||
(newProjectEvent)="openNewProjectDialog()"
|
[completed]="view.tasks.createServiceAccount"
|
||||||
(editProjectEvent)="openEditProject($event)"
|
>
|
||||||
(deleteProjectEvent)="openDeleteProjectDialog($event)"
|
<span>
|
||||||
[projects]="view.latestProjects"
|
{{ "downloadThe" | i18n }} <a bitLink routerLink="">{{ "smCLI" | i18n }}</a>
|
||||||
></sm-projects-list>
|
</span>
|
||||||
<div *ngIf="view.allProjects.length > 0" class="tw-ml-auto tw-mt-4 tw-max-w-max">
|
</sm-onboarding-task>
|
||||||
{{ "showingPortionOfTotal" | i18n: view.latestProjects.length:view.allProjects.length }}
|
<sm-onboarding-task
|
||||||
<a bitLink routerLink="projects" class="tw-ml-2">{{ "viewAll" | i18n }}</a>
|
[title]="'importSecrets' | i18n"
|
||||||
</div>
|
[route]="['settings', 'import']"
|
||||||
</sm-section>
|
icon="bwi-download"
|
||||||
<sm-section>
|
[completed]="view.tasks.importSecrets"
|
||||||
<h2 slot="summary" class="tw-mb-0 tw-text-2xl tw-font-semibold">{{ "secrets" | i18n }}</h2>
|
></sm-onboarding-task>
|
||||||
<sm-secrets-list
|
<sm-onboarding-task
|
||||||
baseRoute="secrets"
|
[title]="'createSecret' | i18n"
|
||||||
(deleteSecretsEvent)="openDeleteSecret($event)"
|
(click)="openSecretDialog()"
|
||||||
(newSecretEvent)="openNewSecretDialog()"
|
icon="bwi-key"
|
||||||
(editSecretEvent)="openEditSecret($event)"
|
[completed]="view.tasks.createSecret"
|
||||||
[secrets]="view.latestSecrets"
|
></sm-onboarding-task>
|
||||||
></sm-secrets-list>
|
<sm-onboarding-task
|
||||||
<div *ngIf="view.allSecrets.length > 0" class="tw-ml-auto tw-mt-4 tw-max-w-max">
|
[title]="'createProject' | i18n"
|
||||||
{{ "showingPortionOfTotal" | i18n: view.latestSecrets.length:view.allSecrets.length }}
|
(click)="openNewProjectDialog()"
|
||||||
<a bitLink routerLink="secrets" class="tw-ml-2">{{ "viewAll" | i18n }}</a>
|
icon="bwi-collection"
|
||||||
</div>
|
[completed]="view.tasks.createProject"
|
||||||
</sm-section>
|
></sm-onboarding-task>
|
||||||
|
</sm-onboarding>
|
||||||
|
|
||||||
|
<div class="tw-mt-6 tw-flex tw-flex-col tw-gap-6">
|
||||||
|
<sm-section>
|
||||||
|
<h2 slot="summary" class="tw-mb-0 tw-text-2xl tw-font-semibold">{{ "projects" | i18n }}</h2>
|
||||||
|
<sm-projects-list
|
||||||
|
(newProjectEvent)="openNewProjectDialog()"
|
||||||
|
(editProjectEvent)="openEditProject($event)"
|
||||||
|
(deleteProjectEvent)="openDeleteProjectDialog($event)"
|
||||||
|
[projects]="view.latestProjects"
|
||||||
|
></sm-projects-list>
|
||||||
|
<div *ngIf="view.allProjects.length > 0" class="tw-ml-auto tw-mt-4 tw-max-w-max">
|
||||||
|
{{ "showingPortionOfTotal" | i18n: view.latestProjects.length:view.allProjects.length }}
|
||||||
|
<a bitLink routerLink="projects" class="tw-ml-2">{{ "viewAll" | i18n }}</a>
|
||||||
|
</div>
|
||||||
|
</sm-section>
|
||||||
|
<sm-section>
|
||||||
|
<h2 slot="summary" class="tw-mb-0 tw-text-2xl tw-font-semibold">{{ "secrets" | i18n }}</h2>
|
||||||
|
<sm-secrets-list
|
||||||
|
baseRoute="secrets"
|
||||||
|
(deleteSecretsEvent)="openDeleteSecret($event)"
|
||||||
|
(newSecretEvent)="openNewSecretDialog()"
|
||||||
|
(editSecretEvent)="openEditSecret($event)"
|
||||||
|
[secrets]="view.latestSecrets"
|
||||||
|
></sm-secrets-list>
|
||||||
|
<div *ngIf="view.allSecrets.length > 0" class="tw-ml-auto tw-mt-4 tw-max-w-max">
|
||||||
|
{{ "showingPortionOfTotal" | i18n: view.latestSecrets.length:view.allSecrets.length }}
|
||||||
|
<a bitLink routerLink="secrets" class="tw-ml-2">{{ "viewAll" | i18n }}</a>
|
||||||
|
</div>
|
||||||
|
</sm-section>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #spinner>
|
<ng-template #spinner>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import {
|
import {
|
||||||
combineLatest,
|
|
||||||
combineLatestWith,
|
|
||||||
map,
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
startWith,
|
|
||||||
Subject,
|
|
||||||
switchMap,
|
switchMap,
|
||||||
|
Subject,
|
||||||
takeUntil,
|
takeUntil,
|
||||||
|
combineLatest,
|
||||||
|
startWith,
|
||||||
|
distinct,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
|
|
||||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||||
@ -21,7 +21,6 @@ import {
|
|||||||
ProjectDeleteOperation,
|
ProjectDeleteOperation,
|
||||||
} from "../projects/dialog/project-delete-dialog.component";
|
} from "../projects/dialog/project-delete-dialog.component";
|
||||||
import {
|
import {
|
||||||
OperationType,
|
|
||||||
ProjectDialogComponent,
|
ProjectDialogComponent,
|
||||||
ProjectOperation,
|
ProjectOperation,
|
||||||
} from "../projects/dialog/project-dialog.component";
|
} from "../projects/dialog/project-dialog.component";
|
||||||
@ -30,41 +29,68 @@ import {
|
|||||||
SecretDeleteDialogComponent,
|
SecretDeleteDialogComponent,
|
||||||
SecretDeleteOperation,
|
SecretDeleteOperation,
|
||||||
} from "../secrets/dialog/secret-delete.component";
|
} from "../secrets/dialog/secret-delete.component";
|
||||||
import { SecretDialogComponent, SecretOperation } from "../secrets/dialog/secret-dialog.component";
|
import {
|
||||||
|
OperationType,
|
||||||
|
SecretDialogComponent,
|
||||||
|
SecretOperation,
|
||||||
|
} from "../secrets/dialog/secret-dialog.component";
|
||||||
import { SecretService } from "../secrets/secret.service";
|
import { SecretService } from "../secrets/secret.service";
|
||||||
|
import {
|
||||||
|
ServiceAccountDialogComponent,
|
||||||
|
ServiceAccountOperation,
|
||||||
|
} from "../service-accounts/dialog/service-account-dialog.component";
|
||||||
|
import { ServiceAccountService } from "../service-accounts/service-account.service";
|
||||||
|
|
||||||
|
type Tasks = {
|
||||||
|
importSecrets: boolean;
|
||||||
|
createSecret: boolean;
|
||||||
|
createProject: boolean;
|
||||||
|
createServiceAccount: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "sm-overview",
|
selector: "sm-overview",
|
||||||
templateUrl: "./overview.component.html",
|
templateUrl: "./overview.component.html",
|
||||||
})
|
})
|
||||||
export class OverviewComponent implements OnInit, OnDestroy {
|
export class OverviewComponent implements OnInit, OnDestroy {
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$: Subject<void> = new Subject<void>();
|
||||||
/**
|
|
||||||
* Number of items to show in tables
|
|
||||||
*/
|
|
||||||
private tableSize = 10;
|
private tableSize = 10;
|
||||||
private organizationId: string;
|
private organizationId: string;
|
||||||
|
|
||||||
protected organizationName: string;
|
protected organizationName: string;
|
||||||
|
|
||||||
protected view$: Observable<{
|
protected view$: Observable<{
|
||||||
allProjects: ProjectListView[];
|
allProjects: ProjectListView[];
|
||||||
allSecrets: SecretListView[];
|
allSecrets: SecretListView[];
|
||||||
latestProjects: ProjectListView[];
|
latestProjects: ProjectListView[];
|
||||||
latestSecrets: SecretListView[];
|
latestSecrets: SecretListView[];
|
||||||
|
tasks: Tasks;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
private projectService: ProjectService,
|
private projectService: ProjectService,
|
||||||
|
private secretService: SecretService,
|
||||||
|
private serviceAccountService: ServiceAccountService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService
|
||||||
private secretService: SecretService
|
) {
|
||||||
) {}
|
/**
|
||||||
|
* We want to remount the `sm-onboarding` component on route change.
|
||||||
|
* The component only toggles its visibility on init and on user dismissal.
|
||||||
|
*/
|
||||||
|
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.route.params
|
const orgId$ = this.route.params.pipe(
|
||||||
|
map((p) => p.organizationId),
|
||||||
|
distinct()
|
||||||
|
);
|
||||||
|
|
||||||
|
orgId$
|
||||||
.pipe(
|
.pipe(
|
||||||
map((params) => this.organizationService.get(params.organizationId)),
|
map((orgId) => this.organizationService.get(orgId)),
|
||||||
takeUntil(this.destroy$)
|
takeUntil(this.destroy$)
|
||||||
)
|
)
|
||||||
.subscribe((org) => {
|
.subscribe((org) => {
|
||||||
@ -72,25 +98,33 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
|||||||
this.organizationName = org.name;
|
this.organizationName = org.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
const projects$ = this.projectService.project$.pipe(
|
const projects$ = combineLatest([
|
||||||
startWith(null),
|
orgId$,
|
||||||
combineLatestWith(this.route.params),
|
this.projectService.project$.pipe(startWith(null)),
|
||||||
switchMap(() => this.getProjects())
|
]).pipe(switchMap(([orgId]) => this.projectService.getProjects(orgId)));
|
||||||
|
|
||||||
|
const secrets$ = combineLatest([orgId$, this.secretService.secret$.pipe(startWith(null))]).pipe(
|
||||||
|
switchMap(([orgId]) => this.secretService.getSecrets(orgId))
|
||||||
);
|
);
|
||||||
|
|
||||||
const secrets$ = this.secretService.secret$.pipe(
|
const serviceAccounts$ = combineLatest([
|
||||||
startWith(null),
|
orgId$,
|
||||||
combineLatestWith(this.route.params),
|
this.serviceAccountService.serviceAccount$.pipe(startWith(null)),
|
||||||
switchMap(() => this.getSecrets())
|
]).pipe(switchMap(([orgId]) => this.serviceAccountService.getServiceAccounts(orgId)));
|
||||||
);
|
|
||||||
|
|
||||||
this.view$ = combineLatest([projects$, secrets$]).pipe(
|
this.view$ = combineLatest([projects$, secrets$, serviceAccounts$]).pipe(
|
||||||
map(([projects, secrets]) => {
|
map(([projects, secrets, serviceAccounts]) => {
|
||||||
return {
|
return {
|
||||||
allProjects: projects,
|
|
||||||
allSecrets: secrets,
|
|
||||||
latestProjects: this.getRecentItems(projects, this.tableSize),
|
latestProjects: this.getRecentItems(projects, this.tableSize),
|
||||||
latestSecrets: this.getRecentItems(secrets, this.tableSize),
|
latestSecrets: this.getRecentItems(secrets, this.tableSize),
|
||||||
|
allProjects: projects,
|
||||||
|
allSecrets: secrets,
|
||||||
|
tasks: {
|
||||||
|
importSecrets: secrets.length > 0,
|
||||||
|
createSecret: secrets.length > 0,
|
||||||
|
createProject: projects.length > 0,
|
||||||
|
createServiceAccount: serviceAccounts.length > 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -111,10 +145,6 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
// Projects ---
|
// Projects ---
|
||||||
|
|
||||||
private async getProjects(): Promise<ProjectListView[]> {
|
|
||||||
return await this.projectService.getProjects(this.organizationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
openEditProject(projectId: string) {
|
openEditProject(projectId: string) {
|
||||||
this.dialogService.open<unknown, ProjectOperation>(ProjectDialogComponent, {
|
this.dialogService.open<unknown, ProjectOperation>(ProjectDialogComponent, {
|
||||||
data: {
|
data: {
|
||||||
@ -134,6 +164,14 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openServiceAccountDialog() {
|
||||||
|
this.dialogService.open<unknown, ServiceAccountOperation>(ServiceAccountDialogComponent, {
|
||||||
|
data: {
|
||||||
|
organizationId: this.organizationId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
openDeleteProjectDialog(event: ProjectListView[]) {
|
openDeleteProjectDialog(event: ProjectListView[]) {
|
||||||
this.dialogService.open<unknown, ProjectDeleteOperation>(ProjectDeleteDialogComponent, {
|
this.dialogService.open<unknown, ProjectDeleteOperation>(ProjectDeleteDialogComponent, {
|
||||||
data: {
|
data: {
|
||||||
@ -144,8 +182,13 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
// Secrets ---
|
// Secrets ---
|
||||||
|
|
||||||
private async getSecrets(): Promise<SecretListView[]> {
|
openSecretDialog() {
|
||||||
return await this.secretService.getSecrets(this.organizationId);
|
this.dialogService.open<unknown, SecretOperation>(SecretDialogComponent, {
|
||||||
|
data: {
|
||||||
|
organizationId: this.organizationId,
|
||||||
|
operation: OperationType.Add,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openEditSecret(secretId: string) {
|
openEditSecret(secretId: string) {
|
||||||
|
@ -2,12 +2,13 @@ import { NgModule } from "@angular/core";
|
|||||||
|
|
||||||
import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
|
import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
|
||||||
|
|
||||||
|
import { OnboardingModule } from "./onboarding.module";
|
||||||
import { OverviewRoutingModule } from "./overview-routing.module";
|
import { OverviewRoutingModule } from "./overview-routing.module";
|
||||||
import { OverviewComponent } from "./overview.component";
|
import { OverviewComponent } from "./overview.component";
|
||||||
import { SectionComponent } from "./section.component";
|
import { SectionComponent } from "./section.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SecretsManagerSharedModule, OverviewRoutingModule],
|
imports: [SecretsManagerSharedModule, OverviewRoutingModule, OnboardingModule],
|
||||||
declarations: [OverviewComponent, SectionComponent],
|
declarations: [OverviewComponent, SectionComponent],
|
||||||
providers: [],
|
providers: [],
|
||||||
})
|
})
|
||||||
|
@ -16,6 +16,7 @@ export * from "./link";
|
|||||||
export * from "./menu";
|
export * from "./menu";
|
||||||
export * from "./multi-select";
|
export * from "./multi-select";
|
||||||
export * from "./navigation";
|
export * from "./navigation";
|
||||||
|
export * from "./progress";
|
||||||
export * from "./radio-button";
|
export * from "./radio-button";
|
||||||
export * from "./table";
|
export * from "./table";
|
||||||
export * from "./tabs";
|
export * from "./tabs";
|
||||||
|
Loading…
Reference in New Issue
Block a user