mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +01:00
[SM-1302] Initial config page (#10196)
* Initial config page * Remove project actions * Add copy projectId method to the project page * Update bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Update bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Update apps/web/src/locales/en/messages.json Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Fix method and string naming * Ensure config component load logic happens after params observed * Remove projectId emitted event * Update bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Adjust load function * Fix config translation * Remove unnecceary async from copy functions * Add project ID translation key * Update bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Simplify load function * Simplify variable definition * Add all machine account projects to the config page * Update bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Remove unused variable * Remove revision date in config project list --------- Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
This commit is contained in:
parent
ea025b9026
commit
cf1f7cc61d
@ -9002,6 +9002,24 @@
|
||||
"purchasedSeatsRemoved": {
|
||||
"message": "purchased seats removed"
|
||||
},
|
||||
"environmentVariables": {
|
||||
"message": "Environment variables"
|
||||
},
|
||||
"organizationId": {
|
||||
"message": "Organization ID"
|
||||
},
|
||||
"projectIds": {
|
||||
"message": "Project IDs"
|
||||
},
|
||||
"projectId": {
|
||||
"message": "Project ID"
|
||||
},
|
||||
"projectsAccessedByMachineAccount": {
|
||||
"message": "The following projects can be accessed by this machine account."
|
||||
},
|
||||
"config": {
|
||||
"message": "Config"
|
||||
},
|
||||
"learnMoreAboutEmergencyAccess": {
|
||||
"message":"Learn more about emergency access"
|
||||
},
|
||||
|
@ -6,4 +6,5 @@ export class ProjectListView {
|
||||
revisionDate: string;
|
||||
read: boolean;
|
||||
write: boolean;
|
||||
linkable: boolean;
|
||||
}
|
||||
|
@ -131,6 +131,7 @@ export class ProjectService {
|
||||
);
|
||||
projectListView.creationDate = s.creationDate;
|
||||
projectListView.revisionDate = s.revisionDate;
|
||||
projectListView.linkable = true;
|
||||
return projectListView;
|
||||
}),
|
||||
);
|
||||
|
@ -0,0 +1,47 @@
|
||||
<div *ngIf="!loading">
|
||||
<div class="tw-p-6 tw-border tw-border-solid tw-border-secondary-600 tw-rounded">
|
||||
<h2 bitTypography="h2">{{ "environmentVariables" | i18n }}</h2>
|
||||
<div class="tw-flex tw-gap-6 tw-pt-4">
|
||||
<bit-form-field class="tw-w-2/5 tw-min-w-80" tw>
|
||||
<bit-label>{{ "identityUrl" | i18n }}</bit-label>
|
||||
<input bitInput type="text" [(ngModel)]="identityUrl" [disabled]="true" />
|
||||
<button
|
||||
bitSuffix
|
||||
type="button"
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
[bitAction]="copyIdentityUrl"
|
||||
></button>
|
||||
</bit-form-field>
|
||||
<bit-form-field class="tw-w-2/5 tw-min-w-80">
|
||||
<bit-label>{{ "apiUrl" | i18n }}</bit-label>
|
||||
<input bitInput type="text" [(ngModel)]="apiUrl" [disabled]="true" />
|
||||
<button bitSuffix type="button" bitIconButton="bwi-clone" [bitAction]="copyApiUrl"></button>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<bit-form-field class="tw-w-2/5 tw-min-w-80">
|
||||
<bit-label>{{ "organizationId" | i18n }}</bit-label>
|
||||
<input bitInput type="text" [(ngModel)]="organizationId" [disabled]="true" />
|
||||
<button
|
||||
bitSuffix
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
[bitAction]="copyOrganizationId"
|
||||
></button>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div class="tw-pt-12">
|
||||
<h2 slot="summary" class="tw-mb-0" bitTypography="h2" noMargin>{{ "projectIds" | i18n }}</h2>
|
||||
<p *ngIf="!hasProjects" class="tw-mt-6">{{ "projectsNoItemsTitle" | i18n }}</p>
|
||||
<p *ngIf="hasProjects" class="tw-mt-4">{{ "projectsAccessedByMachineAccount" | i18n }}</p>
|
||||
<sm-projects-list
|
||||
class="tw-mt-8"
|
||||
*ngIf="hasProjects"
|
||||
[showMenus]="false"
|
||||
[projects]="projects"
|
||||
></sm-projects-list>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="loading" class="tw-items-center tw-justify-center tw-pt-64 tw-text-center">
|
||||
<i class="bwi bwi-spinner bwi-spin bwi-3x"></i>
|
||||
</div>
|
@ -0,0 +1,127 @@
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Params } from "@angular/router";
|
||||
import { Subject, concatMap, takeUntil } from "rxjs";
|
||||
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { ProjectListView } from "../../models/view/project-list.view";
|
||||
import { ProjectService } from "../../projects/project.service";
|
||||
import { AccessPolicyService } from "../../shared/access-policies/access-policy.service";
|
||||
|
||||
class ServiceAccountConfig {
|
||||
organizationId: string;
|
||||
serviceAccountId: string;
|
||||
identityUrl: string;
|
||||
apiUrl: string;
|
||||
projects: ProjectListView[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "sm-service-account-config",
|
||||
templateUrl: "./config.component.html",
|
||||
})
|
||||
export class ServiceAccountConfigComponent implements OnInit, OnDestroy {
|
||||
identityUrl: string;
|
||||
apiUrl: string;
|
||||
organizationId: string;
|
||||
serviceAccountId: string;
|
||||
projects: ProjectListView[];
|
||||
hasProjects = false;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
loading = true;
|
||||
|
||||
constructor(
|
||||
private environmentService: EnvironmentService,
|
||||
private route: ActivatedRoute,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private toastService: ToastService,
|
||||
private i18nService: I18nService,
|
||||
private projectService: ProjectService,
|
||||
private accessPolicyService: AccessPolicyService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.params
|
||||
.pipe(
|
||||
concatMap(async (params: Params) => {
|
||||
return await this.load(params.organizationId, params.serviceAccountId);
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe((smConfig) => {
|
||||
this.identityUrl = smConfig.identityUrl;
|
||||
this.apiUrl = smConfig.apiUrl;
|
||||
this.organizationId = smConfig.organizationId;
|
||||
this.serviceAccountId = smConfig.serviceAccountId;
|
||||
this.projects = smConfig.projects;
|
||||
|
||||
this.hasProjects = smConfig.projects.length > 0;
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
async load(organizationId: string, serviceAccountId: string): Promise<ServiceAccountConfig> {
|
||||
const environment = await this.environmentService.getEnvironment();
|
||||
|
||||
const allProjects = await this.projectService.getProjects(organizationId);
|
||||
const policies = await this.accessPolicyService.getServiceAccountGrantedPolicies(
|
||||
organizationId,
|
||||
serviceAccountId,
|
||||
);
|
||||
|
||||
const projects = policies.grantedProjectPolicies.map((policy) => {
|
||||
return {
|
||||
id: policy.accessPolicy.grantedProjectId,
|
||||
name: policy.accessPolicy.grantedProjectName,
|
||||
organizationId: organizationId,
|
||||
linkable: allProjects.some(
|
||||
(project) => project.id === policy.accessPolicy.grantedProjectId,
|
||||
),
|
||||
} as ProjectListView;
|
||||
});
|
||||
|
||||
return {
|
||||
organizationId: organizationId,
|
||||
serviceAccountId: serviceAccountId,
|
||||
identityUrl: environment.getIdentityUrl(),
|
||||
apiUrl: environment.getApiUrl(),
|
||||
projects: projects,
|
||||
} as ServiceAccountConfig;
|
||||
}
|
||||
|
||||
copyIdentityUrl = () => {
|
||||
this.platformUtilsService.copyToClipboard(this.identityUrl);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("valueCopied", this.i18nService.t("identityUrl")),
|
||||
});
|
||||
};
|
||||
|
||||
copyApiUrl = () => {
|
||||
this.platformUtilsService.copyToClipboard(this.apiUrl);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("valueCopied", this.i18nService.t("apiUrl")),
|
||||
});
|
||||
};
|
||||
|
||||
copyOrganizationId = () => {
|
||||
this.platformUtilsService.copyToClipboard(this.organizationId);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("valueCopied", this.i18nService.t("organizationId")),
|
||||
});
|
||||
};
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@
|
||||
</div>
|
||||
</bit-tab-link>
|
||||
<bit-tab-link [route]="['events']">{{ "eventLogs" | i18n }}</bit-tab-link>
|
||||
<bit-tab-link [route]="['config']">{{ "config" | i18n }}</bit-tab-link>
|
||||
</bit-tab-nav-bar>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -2,6 +2,7 @@ import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { AccessTokenComponent } from "./access/access-tokens.component";
|
||||
import { ServiceAccountConfigComponent } from "./config/config.component";
|
||||
import { ServiceAccountEventsComponent } from "./event-logs/service-accounts-events.component";
|
||||
import { serviceAccountAccessGuard } from "./guards/service-account-access.guard";
|
||||
import { ServiceAccountPeopleComponent } from "./people/service-account-people.component";
|
||||
@ -40,6 +41,10 @@ const routes: Routes = [
|
||||
path: "events",
|
||||
component: ServiceAccountEventsComponent,
|
||||
},
|
||||
{
|
||||
path: "config",
|
||||
component: ServiceAccountConfigComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -9,6 +9,7 @@ import { AccessTokenComponent } from "./access/access-tokens.component";
|
||||
import { AccessTokenCreateDialogComponent } from "./access/dialogs/access-token-create-dialog.component";
|
||||
import { AccessTokenDialogComponent } from "./access/dialogs/access-token-dialog.component";
|
||||
import { ExpirationOptionsComponent } from "./access/dialogs/expiration-options.component";
|
||||
import { ServiceAccountConfigComponent } from "./config/config.component";
|
||||
import { ServiceAccountDeleteDialogComponent } from "./dialog/service-account-delete-dialog.component";
|
||||
import { ServiceAccountDialogComponent } from "./dialog/service-account-dialog.component";
|
||||
import { ServiceAccountEventsComponent } from "./event-logs/service-accounts-events.component";
|
||||
@ -28,6 +29,7 @@ import { ServiceAccountsComponent } from "./service-accounts.component";
|
||||
AccessTokenDialogComponent,
|
||||
ExpirationOptionsComponent,
|
||||
ServiceAccountComponent,
|
||||
ServiceAccountConfigComponent,
|
||||
ServiceAccountDeleteDialogComponent,
|
||||
ServiceAccountDialogComponent,
|
||||
ServiceAccountEventsComponent,
|
||||
|
@ -20,7 +20,7 @@
|
||||
<bit-table *ngIf="projects?.length >= 1" [dataSource]="dataSource">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell class="tw-w-0">
|
||||
<th bitCell class="tw-w-0" *ngIf="showMenus">
|
||||
<label class="!tw-mb-0 tw-flex tw-w-fit tw-gap-2 !tw-font-bold !tw-text-muted">
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -32,7 +32,7 @@
|
||||
</label>
|
||||
</th>
|
||||
<th bitCell bitSortable="name" default>{{ "name" | i18n }}</th>
|
||||
<th bitCell bitSortable="revisionDate">{{ "lastEdited" | i18n }}</th>
|
||||
<th bitCell bitSortable="revisionDate" *ngIf="showMenus">{{ "lastEdited" | i18n }}</th>
|
||||
<th
|
||||
bitCell
|
||||
class="tw-w-0"
|
||||
@ -45,13 +45,14 @@
|
||||
[bitMenuTriggerFor]="tableMenu"
|
||||
[title]="'options' | i18n"
|
||||
[attr.aria-label]="'options' | i18n"
|
||||
*ngIf="showMenus"
|
||||
></button>
|
||||
</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body let-rows$>
|
||||
<tr bitRow *ngFor="let project of rows$ | async">
|
||||
<td bitCell>
|
||||
<td bitCell *ngIf="showMenus">
|
||||
<input
|
||||
type="checkbox"
|
||||
(change)="$event ? selection.toggle(project.id) : null"
|
||||
@ -61,12 +62,32 @@
|
||||
<td bitCell>
|
||||
<div class="tw-flex tw-items-center tw-gap-4 tw-break-all">
|
||||
<i class="bwi bwi-collection tw-text-muted" aria-hidden="true"></i>
|
||||
<a bitLink [routerLink]="['/sm', project.organizationId, 'projects', project.id]">{{
|
||||
project.name
|
||||
}}</a>
|
||||
<div>
|
||||
<a
|
||||
*ngIf="project.linkable"
|
||||
bitLink
|
||||
[routerLink]="['/sm', project.organizationId, 'projects', project.id]"
|
||||
>{{ project.name }}</a
|
||||
>
|
||||
<span *ngIf="!project.linkable">{{ project.name }}</span>
|
||||
<div class="tw-text-sm tw-text-muted tw-block">
|
||||
{{ project.id }}
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
buttonType="main"
|
||||
size="small"
|
||||
[title]="'copyUuid' | i18n"
|
||||
[attr.aria-label]="'copyUuid' | i18n"
|
||||
(click)="copyProjectUuidToClipboard(project.id)"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td bitCell class="tw-whitespace-nowrap">{{ project.revisionDate | date: "medium" }}</td>
|
||||
<td bitCell class="tw-whitespace-nowrap" *ngIf="showMenus">
|
||||
{{ project.revisionDate | date: "medium" }}
|
||||
</td>
|
||||
<td bitCell>
|
||||
<button
|
||||
type="button"
|
||||
@ -75,6 +96,7 @@
|
||||
[bitMenuTriggerFor]="projectMenu"
|
||||
[title]="'options' | i18n"
|
||||
[attr.aria-label]="'options' | i18n"
|
||||
*ngIf="showMenus"
|
||||
></button>
|
||||
</td>
|
||||
<bit-menu #projectMenu>
|
||||
|
@ -24,6 +24,8 @@ export class ProjectsListComponent {
|
||||
}
|
||||
private _projects: ProjectListView[];
|
||||
|
||||
@Input() showMenus?: boolean = true;
|
||||
|
||||
@Input()
|
||||
set search(search: string) {
|
||||
this.selection.clear();
|
||||
@ -33,6 +35,7 @@ export class ProjectsListComponent {
|
||||
@Output() editProjectEvent = new EventEmitter<string>();
|
||||
@Output() deleteProjectEvent = new EventEmitter<ProjectListView[]>();
|
||||
@Output() newProjectEvent = new EventEmitter();
|
||||
@Output() copiedProjectUUIdEvent = new EventEmitter<string>();
|
||||
|
||||
selection = new SelectionModel<string>(true, []);
|
||||
protected dataSource = new TableDataSource<ProjectListView>();
|
||||
@ -90,4 +93,13 @@ export class ProjectsListComponent {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
copyProjectUuidToClipboard(id: string) {
|
||||
this.platformUtilsService.copyToClipboard(id);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("valueCopied", this.i18nService.t("projectId")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import {
|
||||
CardComponent,
|
||||
MultiSelectModule,
|
||||
SearchModule,
|
||||
SelectModule,
|
||||
NoItemsModule,
|
||||
FormFieldModule,
|
||||
} from "@bitwarden/components";
|
||||
import { CoreOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/core";
|
||||
import { DynamicAvatarComponent } from "@bitwarden/web-vault/app/components/dynamic-avatar.component";
|
||||
@ -31,17 +33,21 @@ import { SecretsListComponent } from "./secrets-list.component";
|
||||
DynamicAvatarComponent,
|
||||
SearchModule,
|
||||
HeaderModule,
|
||||
CardComponent,
|
||||
FormFieldModule,
|
||||
],
|
||||
exports: [
|
||||
AccessPolicySelectorComponent,
|
||||
BulkConfirmationDialogComponent,
|
||||
BulkStatusDialogComponent,
|
||||
FormFieldModule,
|
||||
HeaderModule,
|
||||
NewMenuComponent,
|
||||
NoItemsModule,
|
||||
ProjectsListComponent,
|
||||
SearchModule,
|
||||
SecretsListComponent,
|
||||
CardComponent,
|
||||
SelectModule,
|
||||
SharedModule,
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user