mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
[SM-325] update header layout to support projected content (#4509)
* expose breadcrumbs to shared module; remove extra whitespace from breadcrumb * update sm-header to accept projected content; update layout style & semantics * remove searchTitle route data; change title to titleId * update styles * update SM pages to use new header * add button slot to header * remove breadcrumbs from shared module * update project tabs * update content projection to use slot attribute * hide tabs container * add icon input to header * add breadcrumbs to service account page * update padding * update styles * code style changes * create access token button component * simplify access token logic * add create access token button to header * update sm-no-items content projection * revert irrelevant commits * add header title slot; rename template vars for clarity * add storybook stories * remove sm-new-menu from default view; refactor search/button content projection * remove unused styles; fix title truncate * remove unnecessary classes Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> --------- Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
@@ -1,9 +1,29 @@
|
|||||||
<div class="tw-mb-3 tw-flex tw-items-center tw-gap-2" *ngIf="routeData$ | async as routeData">
|
<header
|
||||||
<h1 class="tw-m-0 tw-mr-2 tw-text-3xl tw-font-semibold">{{ routeData.title | i18n }}</h1>
|
*ngIf="routeData$ | async as routeData"
|
||||||
<div class="tw-ml-auto tw-w-1/4">
|
class="-tw-m-6 tw-mb-3 tw-flex tw-flex-col tw-p-6"
|
||||||
<input bitInput class="search tw-w-full" placeholder="{{ routeData.searchTitle | i18n }}" />
|
[ngClass]="{
|
||||||
|
'tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 tw-bg-background-alt tw-pb-0':
|
||||||
|
tabsContainer.childElementCount !== 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="tw-flex">
|
||||||
|
<div class="tw-flex tw-min-w-0 tw-flex-1 tw-flex-col tw-gap-2">
|
||||||
|
<ng-content select="[slot=breadcrumbs]"></ng-content>
|
||||||
|
<div #titleContainer [ngClass]="{ 'tw-hidden': titleContainer.childElementCount === 0 }">
|
||||||
|
<ng-content select="[slot=title]"></ng-content>
|
||||||
</div>
|
</div>
|
||||||
<sm-new-menu></sm-new-menu>
|
<h1
|
||||||
|
*ngIf="titleContainer.childElementCount === 0"
|
||||||
|
class="tw-m-0 tw-mr-2 tw-truncate tw-text-3xl tw-font-semibold tw-leading-10"
|
||||||
|
[title]="title || (routeData.titleId | i18n)"
|
||||||
|
>
|
||||||
|
<i *ngIf="icon" class="bwi {{ icon }}" aria-hidden="true"></i>
|
||||||
|
{{ title || (routeData.titleId | i18n) }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="tw-ml-auto tw-flex tw-flex-col tw-gap-4">
|
||||||
|
<div class="tw-flex tw-min-w-max tw-items-center tw-justify-end tw-gap-2">
|
||||||
|
<ng-content></ng-content>
|
||||||
<product-switcher></product-switcher>
|
<product-switcher></product-switcher>
|
||||||
<ng-container *ngIf="account$ | async as account">
|
<ng-container *ngIf="account$ | async as account">
|
||||||
<button [bitMenuTriggerFor]="accountMenu" class="tw-border-0 tw-bg-transparent tw-p-0">
|
<button [bitMenuTriggerFor]="accountMenu" class="tw-border-0 tw-bg-transparent tw-p-0">
|
||||||
@@ -16,7 +36,10 @@
|
|||||||
class="tw-flex tw-items-center tw-py-1 tw-px-4 tw-leading-tight tw-text-info"
|
class="tw-flex tw-items-center tw-py-1 tw-px-4 tw-leading-tight tw-text-info"
|
||||||
appStopProp
|
appStopProp
|
||||||
>
|
>
|
||||||
<bit-avatar [text]="account.name || account.email" [id]="account.userId"></bit-avatar>
|
<bit-avatar
|
||||||
|
[text]="account.name || account.email"
|
||||||
|
[id]="account.userId"
|
||||||
|
></bit-avatar>
|
||||||
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
|
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
|
||||||
<span>{{ "loggedInAs" | i18n }}</span>
|
<span>{{ "loggedInAs" | i18n }}</span>
|
||||||
<small class="tw-block tw-overflow-hidden tw-whitespace-nowrap tw-text-muted">
|
<small class="tw-block tw-overflow-hidden tw-whitespace-nowrap tw-text-muted">
|
||||||
@@ -53,4 +76,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</bit-menu>
|
</bit-menu>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="tw-ml-auto"
|
||||||
|
#contentContainer
|
||||||
|
[ngClass]="{ 'tw-hidden': contentContainer.childElementCount === 0 }"
|
||||||
|
>
|
||||||
|
<ng-content select="[slot=secondary]"></ng-content>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
#tabsContainer
|
||||||
|
class="-tw-mx-4 -tw-mb-px"
|
||||||
|
[ngClass]="{ 'tw-hidden': tabsContainer.childElementCount === 0 }"
|
||||||
|
>
|
||||||
|
<ng-content select="[slot=tabs]"></ng-content>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|||||||
@@ -10,18 +10,24 @@ import { AccountProfile } from "@bitwarden/common/models/domain/account";
|
|||||||
templateUrl: "./header.component.html",
|
templateUrl: "./header.component.html",
|
||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent {
|
||||||
|
/**
|
||||||
|
* Custom title that overrides the route data `titleId`
|
||||||
|
*/
|
||||||
@Input() title: string;
|
@Input() title: string;
|
||||||
@Input() searchTitle: string;
|
|
||||||
|
|
||||||
protected routeData$: Observable<{ title: string; searchTitle: string }>;
|
/**
|
||||||
|
* Icon to show before the title
|
||||||
|
*/
|
||||||
|
@Input() icon: string;
|
||||||
|
|
||||||
|
protected routeData$: Observable<{ titleId: string }>;
|
||||||
protected account$: Observable<AccountProfile>;
|
protected account$: Observable<AccountProfile>;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private stateService: StateService) {
|
constructor(private route: ActivatedRoute, private stateService: StateService) {
|
||||||
this.routeData$ = this.route.data.pipe(
|
this.routeData$ = this.route.data.pipe(
|
||||||
map((params) => {
|
map((params) => {
|
||||||
return {
|
return {
|
||||||
title: params.title,
|
titleId: params.titleId,
|
||||||
searchTitle: params.searchTitle,
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import { Component, Injectable } from "@angular/core";
|
||||||
|
import { RouterModule } from "@angular/router";
|
||||||
|
import { Meta, Story, moduleMetadata, componentWrapperDecorator } from "@storybook/angular";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||||
|
import {
|
||||||
|
BreadcrumbsModule,
|
||||||
|
ButtonModule,
|
||||||
|
NavigationModule,
|
||||||
|
IconModule,
|
||||||
|
IconButtonModule,
|
||||||
|
TabsModule,
|
||||||
|
} from "@bitwarden/components";
|
||||||
|
import { InputModule } from "@bitwarden/components/src/input/input.module";
|
||||||
|
import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/app/tests/preloaded-english-i18n.module";
|
||||||
|
|
||||||
|
import { HeaderComponent } from "./header.component";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class MockStateService {
|
||||||
|
activeAccount$ = new Observable();
|
||||||
|
accounts$ = new Observable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "product-switcher",
|
||||||
|
template: `<button bitIconButton="bwi-filter"></button>`,
|
||||||
|
})
|
||||||
|
class MockProductSwitcher {}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Web/Header",
|
||||||
|
component: HeaderComponent,
|
||||||
|
decorators: [
|
||||||
|
componentWrapperDecorator(
|
||||||
|
(story) => `<div class="tw-min-h-screen tw-flex-1 tw-p-6 tw-text-main">${story}</div>`
|
||||||
|
),
|
||||||
|
moduleMetadata({
|
||||||
|
imports: [
|
||||||
|
JslibModule,
|
||||||
|
RouterModule.forRoot(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: HeaderComponent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ useHash: true }
|
||||||
|
),
|
||||||
|
BreadcrumbsModule,
|
||||||
|
ButtonModule,
|
||||||
|
InputModule,
|
||||||
|
IconModule,
|
||||||
|
IconButtonModule,
|
||||||
|
NavigationModule,
|
||||||
|
PreloadedEnglishI18nModule,
|
||||||
|
TabsModule,
|
||||||
|
],
|
||||||
|
declarations: [HeaderComponent, MockProductSwitcher],
|
||||||
|
providers: [{ provide: StateService, useClass: MockStateService }],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
export const KitchenSink: Story = (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<sm-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug">
|
||||||
|
<bit-breadcrumbs slot="breadcrumbs">
|
||||||
|
<bit-breadcrumb>Foo</bit-breadcrumb>
|
||||||
|
<bit-breadcrumb>Bar</bit-breadcrumb>
|
||||||
|
</bit-breadcrumbs>
|
||||||
|
<input
|
||||||
|
bitInput
|
||||||
|
placeholder="Ask Jeeves"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<button bitButton buttonType="primary">New</button>
|
||||||
|
<button bitButton slot="secondary">Click Me 🎉</button>
|
||||||
|
<bit-tab-nav-bar slot="tabs">
|
||||||
|
<bit-tab-link route="">Foo</bit-tab-link>
|
||||||
|
<bit-tab-link route="#bar">Bar</bit-tab-link>
|
||||||
|
</bit-tab-nav-bar>
|
||||||
|
</sm-header>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Basic: Story = (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<sm-header title="Foobar" icon="bwi-bug"></sm-header>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WithLongTitle: Story = (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<sm-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug"></sm-header>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WithBreadcrumbs: Story = (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<sm-header title="Foobar" icon="bwi-bug" class="tw-text-main">
|
||||||
|
<bit-breadcrumbs slot="breadcrumbs">
|
||||||
|
<bit-breadcrumb>Foo</bit-breadcrumb>
|
||||||
|
<bit-breadcrumb>Bar</bit-breadcrumb>
|
||||||
|
</bit-breadcrumbs>
|
||||||
|
</sm-header>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WithSearch: Story = (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<sm-header title="Foobar" icon="bwi-bug" class="tw-text-main">
|
||||||
|
<input
|
||||||
|
bitInput
|
||||||
|
placeholder="Ask Jeeves"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</sm-header>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WithSecondaryContent: Story = (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<sm-header title="Foobar" icon="bwi-bug" class="tw-text-main">
|
||||||
|
<button bitButton slot="secondary">Click Me 🎉</button>
|
||||||
|
</sm-header>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WithTabs: Story = (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<sm-header title="Foobar" icon="bwi-bug" class="tw-text-main">
|
||||||
|
<bit-tab-nav-bar slot="tabs">
|
||||||
|
<bit-tab-link route="">Foo</bit-tab-link>
|
||||||
|
<bit-tab-link route="#bar">Bar</bit-tab-link>
|
||||||
|
</bit-tab-nav-bar>
|
||||||
|
</sm-header>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WithCustomTitleComponent: Story = (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<sm-header title="Foobar" icon="bwi-bug" class="tw-text-main">
|
||||||
|
<h1 slot="title" class="tw-text-3xl tw-font-semibold" style="font-family: 'Comic Sans MS'">Bitwarden</h1>
|
||||||
|
</sm-header>
|
||||||
|
`,
|
||||||
|
});
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<div class="tw-flex tw-w-full">
|
<div class="tw-flex tw-w-full">
|
||||||
<aside class="tw-min-h-screen tw-w-60 tw-bg-background-alt3">
|
<nav class="tw-min-h-screen tw-w-60 tw-bg-background-alt3">
|
||||||
<router-outlet name="sidebar"></router-outlet>
|
<router-outlet name="sidebar"></router-outlet>
|
||||||
</aside>
|
</nav>
|
||||||
<main class="tw-min-h-screen tw-flex-1 tw-bg-background-alt tw-p-6">
|
<main class="tw-min-h-screen tw-flex-1 tw-p-6">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<button bitButton buttonType="primary" [bitMenuTriggerFor]="newMenu">
|
<button bitButton class="tw-min-w-max" buttonType="primary" [bitMenuTriggerFor]="newMenu">
|
||||||
{{ "new" | i18n }} <i class="bwi bwi-angle-down" aria-hidden="true"></i>
|
{{ "new" | i18n }} <i class="bwi bwi-angle-down" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
<sm-header></sm-header>
|
<sm-header>
|
||||||
|
<sm-new-menu></sm-new-menu>
|
||||||
|
</sm-header>
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
<sm-header title="project" searchTitle="searchProjects"></sm-header>
|
<sm-header *ngIf="project$ | async as project" [title]="project.name" icon="bwi-collection">
|
||||||
|
<bit-breadcrumbs slot="breadcrumbs">
|
||||||
<bit-tab-nav-bar label="Main">
|
<bit-breadcrumb [route]="['..']" icon="bwi-angle-left">{{ "projects" | i18n }}</bit-breadcrumb>
|
||||||
<bit-tab-link [route]="['secrets']">Secrets</bit-tab-link>
|
</bit-breadcrumbs>
|
||||||
<bit-tab-link [route]="['access']">Access</bit-tab-link>
|
<bit-tab-nav-bar label="Main" slot="tabs">
|
||||||
</bit-tab-nav-bar>
|
<bit-tab-link [route]="['secrets']">{{ "secrets" | i18n }}</bit-tab-link>
|
||||||
|
<bit-tab-link [route]="['people']">{{ "people" | i18n }}</bit-tab-link>
|
||||||
|
<bit-tab-link [route]="['serviceAccounts']">{{ "serviceAccounts" | i18n }}</bit-tab-link>
|
||||||
|
</bit-tab-nav-bar>
|
||||||
|
<sm-new-menu></sm-new-menu>
|
||||||
|
</sm-header>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import { ProjectService } from "../project.service";
|
|||||||
templateUrl: "./project.component.html",
|
templateUrl: "./project.component.html",
|
||||||
})
|
})
|
||||||
export class ProjectComponent implements OnInit {
|
export class ProjectComponent implements OnInit {
|
||||||
project: Observable<ProjectView>;
|
project$: Observable<ProjectView>;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private projectService: ProjectService) {}
|
constructor(private route: ActivatedRoute, private projectService: ProjectService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.project = this.route.params.pipe(
|
this.project$ = this.route.params.pipe(
|
||||||
switchMap((params) => {
|
switchMap((params) => {
|
||||||
return this.projectService.getByProjectId(params.projectId);
|
return this.projectService.getByProjectId(params.projectId);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
|
import { BreadcrumbsModule } from "@bitwarden/components";
|
||||||
|
|
||||||
import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
|
import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
|
||||||
|
|
||||||
import { ProjectDeleteDialogComponent } from "./dialog/project-delete-dialog.component";
|
import { ProjectDeleteDialogComponent } from "./dialog/project-delete-dialog.component";
|
||||||
@@ -11,7 +13,7 @@ import { ProjectsRoutingModule } from "./projects-routing.module";
|
|||||||
import { ProjectsComponent } from "./projects/projects.component";
|
import { ProjectsComponent } from "./projects/projects.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SecretsManagerSharedModule, ProjectsRoutingModule],
|
imports: [SecretsManagerSharedModule, ProjectsRoutingModule, BreadcrumbsModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
ProjectsComponent,
|
ProjectsComponent,
|
||||||
ProjectsListComponent,
|
ProjectsListComponent,
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<sm-header></sm-header>
|
<sm-header>
|
||||||
|
<input bitInput [placeholder]="'searchProjects' | i18n" />
|
||||||
|
<sm-new-menu></sm-new-menu>
|
||||||
|
</sm-header>
|
||||||
<sm-projects-list
|
<sm-projects-list
|
||||||
(newProjectEvent)="openNewProjectDialog()"
|
(newProjectEvent)="openNewProjectDialog()"
|
||||||
(editProjectEvent)="openEditProject($event)"
|
(editProjectEvent)="openEditProject($event)"
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<sm-header></sm-header>
|
<sm-header>
|
||||||
|
<input bitInput [placeholder]="'searchSecrets' | i18n" />
|
||||||
|
<sm-new-menu></sm-new-menu>
|
||||||
|
</sm-header>
|
||||||
<sm-secrets-list
|
<sm-secrets-list
|
||||||
(deleteSecretsEvent)="openDeleteSecret($event)"
|
(deleteSecretsEvent)="openDeleteSecret($event)"
|
||||||
(newSecretEvent)="openNewSecretDialog()"
|
(newSecretEvent)="openNewSecretDialog()"
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
<sm-header></sm-header>
|
<sm-header
|
||||||
|
*ngIf="serviceAccount$ | async as serviceAccount"
|
||||||
<bit-tab-nav-bar label="Main">
|
[title]="serviceAccount.name"
|
||||||
|
icon="bwi-wrench"
|
||||||
|
>
|
||||||
|
<bit-breadcrumbs slot="breadcrumbs">
|
||||||
|
<bit-breadcrumb [route]="['..']" icon="bwi-angle-left">{{
|
||||||
|
"serviceAccounts" | i18n
|
||||||
|
}}</bit-breadcrumb>
|
||||||
|
</bit-breadcrumbs>
|
||||||
|
<sm-new-menu></sm-new-menu>
|
||||||
|
<bit-tab-nav-bar label="Main" slot="tabs">
|
||||||
<bit-tab-link [route]="['secrets']">{{ "secrets" | i18n }}</bit-tab-link>
|
<bit-tab-link [route]="['secrets']">{{ "secrets" | i18n }}</bit-tab-link>
|
||||||
<bit-tab-link [route]="['people']">{{ "people" | i18n }}</bit-tab-link>
|
<bit-tab-link [route]="['people']">{{ "people" | i18n }}</bit-tab-link>
|
||||||
<bit-tab-link [route]="['access']">{{ "accessTokens" | i18n }}</bit-tab-link>
|
<bit-tab-link [route]="['access']">{{ "accessTokens" | i18n }}</bit-tab-link>
|
||||||
</bit-tab-nav-bar>
|
</bit-tab-nav-bar>
|
||||||
|
</sm-header>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|||||||
@@ -1,7 +1,27 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { switchMap } from "rxjs";
|
||||||
|
|
||||||
|
import { ServiceAccountService } from "./service-account.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "sm-service-account",
|
selector: "sm-service-account",
|
||||||
templateUrl: "./service-account.component.html",
|
templateUrl: "./service-account.component.html",
|
||||||
})
|
})
|
||||||
export class ServiceAccountComponent {}
|
export class ServiceAccountComponent {
|
||||||
|
/**
|
||||||
|
* TODO: remove when a server method is available that fetches a service account by ID
|
||||||
|
*/
|
||||||
|
protected serviceAccount$ = this.route.params.pipe(
|
||||||
|
switchMap((params) =>
|
||||||
|
this.serviceAccountService
|
||||||
|
.getServiceAccounts(params.organizationId)
|
||||||
|
.then((saList) => saList.find((sa) => sa.id === params.serviceAccountId))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private serviceAccountService: ServiceAccountService
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<sm-header></sm-header>
|
<sm-header>
|
||||||
|
<input bitInput [placeholder]="'searchServiceAccounts' | i18n" />
|
||||||
|
<sm-new-menu></sm-new-menu>
|
||||||
|
</sm-header>
|
||||||
<sm-service-accounts-list
|
<sm-service-accounts-list
|
||||||
[serviceAccounts]="serviceAccounts$ | async"
|
[serviceAccounts]="serviceAccounts$ | async"
|
||||||
(newServiceAccountEvent)="openNewServiceAccountDialog()"
|
(newServiceAccountEvent)="openNewServiceAccountDialog()"
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
|
import { BreadcrumbsModule } from "@bitwarden/components";
|
||||||
|
|
||||||
import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
|
import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
|
||||||
|
|
||||||
import { AccessListComponent } from "./access/access-list.component";
|
import { AccessListComponent } from "./access/access-list.component";
|
||||||
@@ -14,7 +16,7 @@ import { ServiceAccountsRoutingModule } from "./service-accounts-routing.module"
|
|||||||
import { ServiceAccountsComponent } from "./service-accounts.component";
|
import { ServiceAccountsComponent } from "./service-accounts.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SecretsManagerSharedModule, ServiceAccountsRoutingModule],
|
imports: [SecretsManagerSharedModule, ServiceAccountsRoutingModule, BreadcrumbsModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
AccessListComponent,
|
AccessListComponent,
|
||||||
ExpirationOptionsComponent,
|
ExpirationOptionsComponent,
|
||||||
|
|||||||
@@ -31,24 +31,21 @@ const routes: Routes = [
|
|||||||
path: "secrets",
|
path: "secrets",
|
||||||
loadChildren: () => SecretsModule,
|
loadChildren: () => SecretsModule,
|
||||||
data: {
|
data: {
|
||||||
title: "secrets",
|
titleId: "secrets",
|
||||||
searchTitle: "searchSecrets",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "projects",
|
path: "projects",
|
||||||
loadChildren: () => ProjectsModule,
|
loadChildren: () => ProjectsModule,
|
||||||
data: {
|
data: {
|
||||||
title: "projects",
|
titleId: "projects",
|
||||||
searchTitle: "searchProjects",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "service-accounts",
|
path: "service-accounts",
|
||||||
loadChildren: () => ServiceAccountsModule,
|
loadChildren: () => ServiceAccountsModule,
|
||||||
data: {
|
data: {
|
||||||
title: "serviceAccounts",
|
titleId: "serviceAccounts",
|
||||||
searchTitle: "searchServiceAccounts",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
<ng-template>
|
<ng-template>
|
||||||
<i *ngIf="icon" class="bwi {{ icon }} tw-mr-1" aria-hidden="true"></i>
|
<i *ngIf="icon" class="bwi {{ icon }} !tw-mr-2" aria-hidden="true"></i><ng-content></ng-content>
|
||||||
<ng-content></ng-content>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import { TabLinkComponent } from "./tab-link.component";
|
|||||||
@Component({
|
@Component({
|
||||||
selector: "bit-tab-nav-bar",
|
selector: "bit-tab-nav-bar",
|
||||||
templateUrl: "tab-nav-bar.component.html",
|
templateUrl: "tab-nav-bar.component.html",
|
||||||
|
host: {
|
||||||
|
class: "tw-block",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export class TabNavBarComponent implements AfterContentInit {
|
export class TabNavBarComponent implements AfterContentInit {
|
||||||
@ContentChildren(forwardRef(() => TabLinkComponent)) tabLabels: QueryList<TabLinkComponent>;
|
@ContentChildren(forwardRef(() => TabLinkComponent)) tabLabels: QueryList<TabLinkComponent>;
|
||||||
|
|||||||
Reference in New Issue
Block a user