diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 1a6489e61b..0c2e7c05cf 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6361,6 +6361,22 @@ "userAccessSecretsManager": { "message": "This user can access the Secrets Manager Beta" }, + "viewAll": { + "message": "View all" + }, + "showingPortionOfTotal": { + "message": "Showing $PORTION$ of $TOTAL$", + "placeholders": { + "portion": { + "content": "$1", + "example": "2" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, "resolveTheErrorsBelowAndTryAgain": { "message": "Resolve the errors below and try again." }, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html index 2944c84877..aa9c243622 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html @@ -1,3 +1,41 @@ - + +
+ +

{{ "projects" | i18n }}

+ +
+ {{ "showingPortionOfTotal" | i18n: view.latestProjects.length:view.allProjects.length }} + {{ "viewAll" | i18n }} +
+
+ +

{{ "secrets" | i18n }}

+ +
+ {{ "showingPortionOfTotal" | i18n: view.latestSecrets.length:view.allSecrets.length }} + {{ "viewAll" | i18n }} +
+
+
+ + +
+ +
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index 308459eb6e..97cce8d2b8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -1,7 +1,177 @@ -import { Component } from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { + combineLatest, + combineLatestWith, + map, + Observable, + startWith, + Subject, + switchMap, + takeUntil, +} from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; +import { DialogService } from "@bitwarden/components"; + +import { ProjectListView } from "../models/view/project-list.view"; +import { SecretListView } from "../models/view/secret-list.view"; +import { + ProjectDeleteDialogComponent, + ProjectDeleteOperation, +} from "../projects/dialog/project-delete-dialog.component"; +import { + OperationType, + ProjectDialogComponent, + ProjectOperation, +} from "../projects/dialog/project-dialog.component"; +import { ProjectService } from "../projects/project.service"; +import { + SecretDeleteDialogComponent, + SecretDeleteOperation, +} from "../secrets/dialog/secret-delete.component"; +import { SecretDialogComponent, SecretOperation } from "../secrets/dialog/secret-dialog.component"; +import { SecretService } from "../secrets/secret.service"; @Component({ selector: "sm-overview", templateUrl: "./overview.component.html", }) -export class OverviewComponent {} +export class OverviewComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); + /** + * Number of items to show in tables + */ + private tableSize = 10; + private organizationId: string; + + protected organizationName: string; + protected view$: Observable<{ + allProjects: ProjectListView[]; + allSecrets: SecretListView[]; + latestProjects: ProjectListView[]; + latestSecrets: SecretListView[]; + }>; + + constructor( + private route: ActivatedRoute, + private projectService: ProjectService, + private dialogService: DialogService, + private organizationService: OrganizationService, + private secretService: SecretService + ) {} + + ngOnInit() { + this.route.params + .pipe( + map((params) => this.organizationService.get(params.organizationId)), + takeUntil(this.destroy$) + ) + .subscribe((org) => { + this.organizationId = org.id; + this.organizationName = org.name; + }); + + const projects$ = this.projectService.project$.pipe( + startWith(null), + combineLatestWith(this.route.params), + switchMap(() => this.getProjects()) + ); + + const secrets$ = this.secretService.secret$.pipe( + startWith(null), + combineLatestWith(this.route.params), + switchMap(() => this.getSecrets()) + ); + + this.view$ = combineLatest([projects$, secrets$]).pipe( + map(([projects, secrets]) => { + return { + allProjects: projects, + allSecrets: secrets, + latestProjects: this.getRecentItems(projects, this.tableSize), + latestSecrets: this.getRecentItems(secrets, this.tableSize), + }; + }) + ); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + private getRecentItems(items: T, length: number): T { + return items + .sort((a, b) => { + return new Date(b.revisionDate).getTime() - new Date(a.revisionDate).getTime(); + }) + .slice(0, length) as T; + } + + // Projects --- + + private async getProjects(): Promise { + return await this.projectService.getProjects(this.organizationId); + } + + openEditProject(projectId: string) { + this.dialogService.open(ProjectDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Edit, + projectId: projectId, + }, + }); + } + + openNewProjectDialog() { + this.dialogService.open(ProjectDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Add, + }, + }); + } + + openDeleteProjectDialog(event: ProjectListView[]) { + this.dialogService.open(ProjectDeleteDialogComponent, { + data: { + projects: event, + }, + }); + } + + // Secrets --- + + private async getSecrets(): Promise { + return await this.secretService.getSecrets(this.organizationId); + } + + openEditSecret(secretId: string) { + this.dialogService.open(SecretDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Edit, + secretId: secretId, + }, + }); + } + + openDeleteSecret(secretIds: string[]) { + this.dialogService.open(SecretDeleteDialogComponent, { + data: { + secretIds: secretIds, + }, + }); + } + + openNewSecretDialog() { + this.dialogService.open(SecretDialogComponent, { + data: { + organizationId: this.organizationId, + operation: OperationType.Add, + }, + }); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts index 2e1cee28c2..9b824ee4d9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts @@ -4,10 +4,11 @@ import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; import { OverviewRoutingModule } from "./overview-routing.module"; import { OverviewComponent } from "./overview.component"; +import { SectionComponent } from "./section.component"; @NgModule({ imports: [SecretsManagerSharedModule, OverviewRoutingModule], - declarations: [OverviewComponent], + declarations: [OverviewComponent, SectionComponent], providers: [], }) export class OverviewModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.html new file mode 100644 index 0000000000..ab7c67c7b2 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.html @@ -0,0 +1,15 @@ +
+
+
+ +
+
+ +
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts new file mode 100644 index 0000000000..bd68e58ea8 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts @@ -0,0 +1,18 @@ +import { Component, Input } from "@angular/core"; + +@Component({ + selector: "sm-section", + templateUrl: "./section.component.html", +}) +export class SectionComponent { + @Input() open = true; + + /** + * UID for `[attr.aria-controls]` + */ + protected contentId = Math.random().toString(36).substring(2); + + protected toggle() { + this.open = !this.open; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts index c6f68db8d8..fe06359e0c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts @@ -11,7 +11,6 @@ import { ProjectPeopleComponent } from "./project/project-people.component"; import { ProjectSecretsComponent } from "./project/project-secrets.component"; import { ProjectServiceAccountsComponent } from "./project/project-service-accounts.component"; import { ProjectComponent } from "./project/project.component"; -import { ProjectsListComponent } from "./projects-list/projects-list.component"; import { ProjectsRoutingModule } from "./projects-routing.module"; import { ProjectsComponent } from "./projects/projects.component"; @@ -19,7 +18,6 @@ import { ProjectsComponent } from "./projects/projects.component"; imports: [SecretsManagerSharedModule, ProjectsRoutingModule, BreadcrumbsModule], declarations: [ ProjectsComponent, - ProjectsListComponent, ProjectAccessComponent, ProjectDialogComponent, ProjectDeleteDialogComponent, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.html similarity index 83% rename from bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html rename to bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.html index 5e34ae9c19..476121a1e5 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.html @@ -31,7 +31,7 @@ {{ "all" | i18n }} - {{ "name" | i18n }} + {{ "name" | i18n }} {{ "lastEdited" | i18n }} - + {{ "viewProject" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts similarity index 96% rename from bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.ts rename to bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts index 869895150a..bc40f17485 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts @@ -4,7 +4,7 @@ import { Subject, takeUntil } from "rxjs"; import { TableDataSource } from "@bitwarden/components"; -import { ProjectListView } from "../../models/view/project-list.view"; +import { ProjectListView } from "../models/view/project-list.view"; @Component({ selector: "sm-projects-list", diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html index 23a4231283..c1ea2e1efd 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html @@ -17,7 +17,7 @@ - + @@ -31,9 +31,9 @@ {{ "all" | i18n }} - {{ "name" | i18n }} + {{ "name" | i18n }} {{ "project" | i18n }} - {{ "lastEdited" | i18n }} + {{ "lastEdited" | i18n }} + +
+ + +
- {{ secret.revisionDate | date: "medium" }} + {{ secret.revisionDate | date: "medium" }} @@ -83,6 +78,9 @@ export class SortableComponent implements OnInit { get classList() { return [ + "tw-group", + "tw-min-w-max", + // Offset to border and padding "-tw-m-1.5", "tw-font-bold",