1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-09 00:11:30 +01:00

[AC-1418] Add secrets manager manage subscription component (#5661)

* [AC-1423] Add minWidth input to bit-progress component

* [AC-1423] Add ProgressModule to shared.module.ts

* [AC-1423] Update cloud subscription page styles

- Remove bootstrap styles
- Use CL components where applicable
- Use CL typography directives
- Update heading levels to prepare for new SM sections

* [AC-1423] Add usePasswordManager boolean to organization domain

* [AC-1423] Introduce BitwardenProductType enum

* [AC-1423] Update Organization subscription line items

- Add product type prefix
- Indent addon services like additional storage and service accounts
- Show line items for free plans

* [AC-1423] Simply sort function

* [AC-1423] Remove header border

* [AC-1423] Remove redundant condition

* [AC-1423] Remove ineffective div

* [AC-1423] Make "Password Manager" the default fallback for product name

* Revert "[AC-1423] Add minWidth input to bit-progress component"

This reverts commit 95b2223a30.

* [AC-1423] Remove minWidth attribute

* [AC-1423] Switch to AddonProductType enum instead of boolean

* Revert "[AC-1423] Switch to AddonProductType enum instead of boolean"

This reverts commit 204f64b4e7.

* [AC-1423] Tweak sorting comment

* [AC-1418] Add initial SecretsManagerAdjustSubscription component

* [AC-1418] Add initial SM adjustment form

* [AC-1418] Adjust organization-subscription-update.request.ts to support both PM and SM

* [AC-1418] Rename service account fields in the options interface

* [AC-1418] Add api service call to update SM subscription

* [AC-1418] Cleanup form html

* [AC-1418] Add missing SM plan properties

* [AC-1418] Add SM subscription adjust form and logic to hide it

* [AC-1418] Add better docs to options interface

* [AC-1418] Fix conflicting required/optional labels for auto-scaling limits

* [AC-1418] Adjust labels and appearance to better match design

* [AC-1418] Use the SM plan for billing interval

* [AC-1418] Hide SM billing adjustment component behind feature flag

* [AC-1418] Update request model to match server

* [AC-1418] Cleanup BitwardenProductType after merge

Add to barrel file and update applicable imports.

* [AC-1418] Revert change to update PM subscription request model

* [AC-1418] Add new update SM subscription request model

* [AC-1418] Add new service method to update SM subscription

* [AC-1418] Use new model and service method

* [AC-1418] Cleanup SM subscription UI flags

* [AC-1418] Move SM adjust subscription component into SM billing module

* [AC-1418] Update SM seat count minimum to 1

* [AC-1418] Add missing currency codes

* [AC-1418] Simplify monthly price calculation

* [AC-1418] Increase PM adjust subscription form input width

* [AC-1418] Add check for null subscription

---------

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
Shane Melton 2023-07-03 15:51:29 -07:00 committed by GitHub
parent 03079735f3
commit 69d601fa78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 447 additions and 43 deletions

View File

@ -1,7 +1,7 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div>
<div class="row">
<div class="form-group col-6">
<div class="form-group col-8">
<label for="newSeatCount">{{ "subscriptionSeats" | i18n }}</label>
<input
id="newSeatCount"
@ -41,7 +41,7 @@
<label for="maxAutoscaleSeats">{{ "maxSeatLimit" | i18n }}</label>
<input
id="maxAutoscaleSeats"
class="form-control col-6"
class="form-control col-8"
type="number"
name="MaxAutoscaleSeats"
[(ngModel)]="newMaxSeats"

View File

@ -177,6 +177,14 @@
></app-adjust-storage>
</div>
</ng-container>
<ng-container *ngIf="showAdjustSecretsManager">
<h3 bitTypography="h3" class="tw-mt-9">{{ "secretsManager" | i18n }}</h3>
<app-sm-adjust-subscription
[organizationId]="organizationId"
[options]="smOptions"
(onAdjusted)="subscriptionAdjusted()"
></app-sm-adjust-subscription>
</ng-container>
</ng-container>
<h2 bitTypography="h2" class="tw-mt-7">{{ "selfHostingTitle" | i18n }}</h2>

View File

@ -9,8 +9,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { PlanType } from "@bitwarden/common/billing/enums";
import { BitwardenProductType } from "@bitwarden/common/billing/enums/bitwarden-product-type.enum";
import { BitwardenProductType, PlanType } from "@bitwarden/common/billing/enums";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@ -23,6 +22,7 @@ import {
BillingSyncApiKeyComponent,
BillingSyncApiModalData,
} from "./billing-sync-api-key.component";
import { SecretsManagerSubscriptionOptions } from "./secrets-manager/sm-adjust-subscription.component";
@Component({
selector: "app-org-subscription-cloud",
@ -38,6 +38,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
adjustStorageAdd = true;
showAdjustStorage = false;
hasBillingSyncToken: boolean;
showAdjustSecretsManager = false;
showSecretsManagerSubscribe = false;
@ -113,15 +114,26 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
this.showSecretsManagerSubscribe =
this.userOrg.canEditSubscription &&
!this.userOrg.useSecretsManager &&
this.subscription != null &&
!this.subscription.cancelled &&
!this.subscriptionMarkedForCancel;
// Remove next line when the sm-ga-billing flag is deleted
this.showSecretsManagerSubscribe =
this.showSecretsManagerSubscribe &&
(await this.configService.getFeatureFlagBool(FeatureFlag.SecretsManagerBilling));
this.showAdjustSecretsManager =
this.userOrg.canEditSubscription &&
this.userOrg.useSecretsManager &&
this.subscription != null &&
this.sub.secretsManagerPlan?.hasAdditionalSeatsOption &&
!this.subscription.cancelled &&
!this.subscriptionMarkedForCancel;
this.loading = false;
// Remove the remaining lines when the sm-ga-billing flag is deleted
const smBillingEnabled = await this.configService.getFeatureFlagBool(
FeatureFlag.SecretsManagerBilling
);
this.showSecretsManagerSubscribe = this.showSecretsManagerSubscribe && smBillingEnabled;
this.showAdjustSecretsManager = this.showAdjustSecretsManager && smBillingEnabled;
}
get subscription() {
@ -169,6 +181,19 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
return this.sub.seats;
}
get smOptions(): SecretsManagerSubscriptionOptions {
return {
seatCount: this.sub.smSeats,
seatLimit: this.sub.maxAutoscaleSmSeats,
seatPrice: this.sub.secretsManagerPlan.seatPrice,
serviceAccountLimit: this.sub.maxAutoscaleSmServiceAccounts,
serviceAccountCount: this.sub.smServiceAccounts,
interval: this.sub.secretsManagerPlan.isAnnual ? "year" : "month",
additionalServiceAccountPrice: this.sub.secretsManagerPlan.additionalPricePerServiceAccount,
baseServiceAccountCount: this.sub.secretsManagerPlan.baseServiceAccount,
};
}
get maxAutoscaleSeats() {
return this.sub.maxAutoscaleSeats;
}

View File

@ -0,0 +1,92 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-form-field class="tw-w-2/3">
<bit-label>{{ "subscriptionSeats" | i18n }}</bit-label>
<input bitInput id="smSeatCount" formControlName="seatCount" type="number" step="1" min="1" />
<bit-hint>
<strong>{{ "total" | i18n }}:</strong>
{{ formGroup.value.seatCount || 0 }} &times; {{ options.seatPrice | currency : "$" }} =
{{ seatTotal | currency : "$" }} / {{ options.interval | i18n }}
</bit-hint>
</bit-form-field>
<bit-form-control>
<bit-label>{{ "limitSubscription" | i18n }}</bit-label>
<input type="checkbox" bitCheckbox id="limitSmSeats" formControlName="limitSeats" />
<bit-hint>
{{ "limitSmSubscriptionDesc" | i18n }}
</bit-hint>
</bit-form-control>
<bit-form-field class="tw-w-2/3" *ngIf="formGroup.value.limitSeats">
<bit-label>{{ "maxSeatLimit" | i18n }}</bit-label>
<input
bitInput
id="smSeatLimit"
formControlName="seatLimit"
type="number"
step="1"
[min]="formGroup.value.seatCount"
/>
<bit-hint>
<strong>{{ "maxSeatCost" | i18n }}:</strong>
{{ formGroup.value.seatLimit || 0 }} &times; {{ options.seatPrice | currency : "$" }} =
{{ maxSeatTotal | currency : "$" }} / {{ options.interval | i18n }}
</bit-hint>
</bit-form-field>
<bit-form-field class="tw-w-2/3">
<bit-label>{{ "additionalServiceAccounts" | i18n }}</bit-label>
<input
bitInput
id="additionalServiceAccountCount"
formControlName="serviceAccountCount"
type="number"
step="1"
min="0"
/>
<bit-hint>
<div>
{{
"additionalServiceAccountsDesc"
| i18n : options.baseServiceAccountCount : (monthlyServiceAccountPrice | currency : "$")
}}
</div>
<div>
<strong>{{ "total" | i18n }}:</strong>
{{ formGroup.value.serviceAccountCount || 0 }} &times;
{{ options.additionalServiceAccountPrice | currency : "$" }} =
{{ serviceAccountTotal | currency : "$" }} / {{ options.interval | i18n }}
</div>
</bit-hint>
</bit-form-field>
<bit-form-control>
<bit-label>{{ "limitServiceAccounts" | i18n }}</bit-label>
<input
type="checkbox"
bitCheckbox
id="limitServiceAccounts"
formControlName="limitServiceAccounts"
/>
<bit-hint>
{{ "limitServiceAccountsDesc" | i18n }}
</bit-hint>
</bit-form-control>
<bit-form-field class="tw-w-2/3" *ngIf="formGroup.value.limitServiceAccounts">
<bit-label>{{ "serviceAccountLimit" | i18n }}</bit-label>
<input
bitInput
id="additionalServiceAccountLimit"
formControlName="serviceAccountLimit"
type="number"
step="1"
[min]="formGroup.value.serviceAccountCount"
/>
<bit-hint>
<strong>{{ "maxServiceAccountCost" | i18n }}:</strong>
{{ formGroup.value.serviceAccountLimit || 0 }} &times;
{{ options.additionalServiceAccountPrice | currency : "$" }} =
{{ maxServiceAccountTotal | currency : "$" }} / {{ options.interval | i18n }}
</bit-hint>
</bit-form-field>
<button type="submit" bitButton buttonType="primary" bitFormButton>
{{ "save" | i18n }}
</button>
<bit-error-summary [formGroup]="formGroup" class="tw-mt-2"></bit-error-summary>
</form>

View File

@ -0,0 +1,168 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationSmSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-sm-subscription-update.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
export interface SecretsManagerSubscriptionOptions {
interval: "year" | "month";
/**
* The current number of seats the organization subscribes to.
*/
seatCount: number;
/**
* Optional auto-scaling limit for the number of seats the organization can subscribe to.
*/
seatLimit: number;
/**
* The price per seat for the subscription.
*/
seatPrice: number;
/**
* The number of service accounts that are included in the base subscription.
*/
baseServiceAccountCount: number;
/**
* The current number of additional service accounts the organization subscribes to.
*/
serviceAccountCount: number;
/**
* Optional auto-scaling limit for the number of additional service accounts the organization can subscribe to.
*/
serviceAccountLimit: number;
/**
* The price per additional service account for the subscription.
*/
additionalServiceAccountPrice: number;
}
@Component({
selector: "app-sm-adjust-subscription",
templateUrl: "sm-adjust-subscription.component.html",
})
export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDestroy {
@Input() organizationId: string;
@Input() options: SecretsManagerSubscriptionOptions;
@Output() onAdjusted = new EventEmitter();
private destroy$ = new Subject<void>();
formGroup = this.formBuilder.group({
seatCount: [0, [Validators.required, Validators.min(1)]],
limitSeats: [false],
seatLimit: [null as number | null],
serviceAccountCount: [0, [Validators.required, Validators.min(0)]],
limitServiceAccounts: [false],
serviceAccountLimit: [null as number | null],
});
get monthlyServiceAccountPrice(): number {
return this.options.interval == "month"
? this.options.additionalServiceAccountPrice
: this.options.additionalServiceAccountPrice / 12;
}
get serviceAccountTotal(): number {
return Math.abs(
this.formGroup.value.serviceAccountCount * this.options.additionalServiceAccountPrice
);
}
get seatTotal(): number {
return Math.abs(this.formGroup.value.seatCount * this.options.seatPrice);
}
get maxServiceAccountTotal(): number {
return Math.abs(
(this.formGroup.value.serviceAccountLimit ?? 0) * this.options.additionalServiceAccountPrice
);
}
get maxSeatTotal(): number {
return Math.abs((this.formGroup.value.seatLimit ?? 0) * this.options.seatPrice);
}
constructor(
private formBuilder: FormBuilder,
private organizationApiService: OrganizationApiServiceAbstraction,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService
) {}
ngOnInit() {
this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
const seatLimitControl = this.formGroup.controls.seatLimit;
const serviceAccountLimitControl = this.formGroup.controls.serviceAccountLimit;
if (value.limitSeats) {
seatLimitControl.setValidators([Validators.min(value.seatCount)]);
seatLimitControl.enable({ emitEvent: false });
} else {
seatLimitControl.disable({ emitEvent: false });
}
if (value.limitServiceAccounts) {
serviceAccountLimitControl.setValidators([Validators.min(value.serviceAccountCount)]);
serviceAccountLimitControl.enable({ emitEvent: false });
} else {
serviceAccountLimitControl.disable({ emitEvent: false });
}
});
this.formGroup.patchValue({
seatCount: this.options.seatCount,
seatLimit: this.options.seatLimit,
serviceAccountCount: this.options.serviceAccountCount,
serviceAccountLimit: this.options.serviceAccountLimit,
limitSeats: this.options.seatLimit != null,
limitServiceAccounts: this.options.serviceAccountLimit != null,
});
}
submit = async () => {
this.formGroup.markAllAsTouched();
if (this.formGroup.invalid) {
return;
}
const seatAdjustment = this.formGroup.value.seatCount - this.options.seatCount;
const serviceAccountAdjustment =
this.formGroup.value.serviceAccountCount - this.options.serviceAccountCount;
const request = new OrganizationSmSubscriptionUpdateRequest(
seatAdjustment,
serviceAccountAdjustment,
this.formGroup.value.seatLimit,
this.formGroup.value.serviceAccountLimit
);
await this.organizationApiService.updateSecretsManagerSubscription(
this.organizationId,
request
);
await this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("subscriptionUpdated")
);
this.onAdjusted.emit();
};
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -2,12 +2,21 @@ import { NgModule } from "@angular/core";
import { SharedModule } from "../../../shared";
import { SecretsManagerAdjustSubscriptionComponent } from "./sm-adjust-subscription.component";
import { SecretsManagerSubscribeStandaloneComponent } from "./sm-subscribe-standalone.component";
import { SecretsManagerSubscribeComponent } from "./sm-subscribe.component";
@NgModule({
imports: [SharedModule],
declarations: [SecretsManagerSubscribeComponent, SecretsManagerSubscribeStandaloneComponent],
exports: [SecretsManagerSubscribeComponent, SecretsManagerSubscribeStandaloneComponent],
declarations: [
SecretsManagerSubscribeComponent,
SecretsManagerSubscribeStandaloneComponent,
SecretsManagerAdjustSubscriptionComponent,
],
exports: [
SecretsManagerSubscribeComponent,
SecretsManagerSubscribeStandaloneComponent,
SecretsManagerAdjustSubscriptionComponent,
],
})
export class SecretsManagerBillingModule {}

View File

@ -20,8 +20,7 @@ import { OrganizationCreateRequest } from "@bitwarden/common/admin-console/model
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request";
import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request";
import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums";
import { BitwardenProductType } from "@bitwarden/common/billing/enums/bitwarden-product-type";
import { BitwardenProductType, PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { ProductType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@ -56,24 +55,29 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
@Input() showFree = true;
@Input() showCancel = false;
@Input() acceptingSponsorship = false;
@Input()
get product(): ProductType {
return this._product;
}
set product(product: ProductType) {
this._product = product;
this.formGroup?.controls?.product?.setValue(product);
}
private _product = ProductType.Free;
@Input()
get plan(): PlanType {
return this._plan;
}
set plan(plan: PlanType) {
this._plan = plan;
this.formGroup?.controls?.plan?.setValue(plan);
}
private _plan = PlanType.Free;
@Input() providerId?: string;
@Output() onSuccess = new EventEmitter<OnSuccessArgs>();

View File

@ -404,8 +404,7 @@
"viewItem": {
"message": "View item"
},
"new":
{
"new": {
"message": "New",
"description": "for adding new items"
},
@ -3324,6 +3323,9 @@
"limitSubscriptionDesc": {
"message": "Set a seat limit for your subscription. Once this limit is reached, you will not be able to invite new members."
},
"limitSmSubscriptionDesc": {
"message": "Set a seat limit for your Secrets Manger subscription. Once this limit is reached, you will not be able to invite new members."
},
"maxSeatLimit": {
"message": "Seat Limit (optional)",
"description": "Upper limit of seats to allow through autoscaling"
@ -5855,10 +5857,10 @@
"message": "Delete secrets",
"description": "The action to delete multiple secrets from the system."
},
"hardDeleteSecret":{
"hardDeleteSecret": {
"message": "Permanently delete secret"
},
"hardDeleteSecrets":{
"hardDeleteSecrets": {
"message": "Permanently delete secrets"
},
"secretProjectAssociationDescription": {
@ -5937,14 +5939,14 @@
"message": "To get started, add a new secret or import secrets.",
"description": "Message to encourage the user to start adding secrets."
},
"secretsTrashNoItemsMessage":{
"secretsTrashNoItemsMessage": {
"message": "There are no secrets in the trash."
},
"serviceAccountsNoItemsMessage": {
"message": "Create a new service account to get started automating secret access.",
"description": "Message to encourage the user to start creating service accounts."
},
"serviceAccountsNoItemsTitle": {
"serviceAccountsNoItemsTitle": {
"message": "Nothing to show yet",
"description": "Title to indicate that there are no service accounts to display."
},
@ -5965,7 +5967,7 @@
"description": "Action to view the details of a service account."
},
"deleteServiceAccountDialogMessage": {
"message": "Deleting service account $SERVICE_ACCOUNT$ is permanent and irreversible.",
"message": "Deleting service account $SERVICE_ACCOUNT$ is permanent and irreversible.",
"placeholders": {
"service_account": {
"content": "$1",
@ -5973,11 +5975,11 @@
}
}
},
"deleteServiceAccountsDialogMessage":{
"deleteServiceAccountsDialogMessage": {
"message": "Deleting service accounts is permanent and irreversible."
},
"deleteServiceAccountsConfirmMessage":{
"message": "Delete $COUNT$ service accounts",
"deleteServiceAccountsConfirmMessage": {
"message": "Delete $COUNT$ service accounts",
"placeholders": {
"count": {
"content": "$1",
@ -5985,19 +5987,19 @@
}
}
},
"deleteServiceAccountToast":{
"deleteServiceAccountToast": {
"message": "Service account deleted"
},
"deleteServiceAccountsToast":{
"deleteServiceAccountsToast": {
"message": "Service accounts deleted"
},
"searchServiceAccounts": {
"message": "Search service accounts",
"description": "Placeholder text for searching service accounts."
},
"editServiceAccount":{
"message":"Edit service account",
"description" : "Title for editing a service account."
"editServiceAccount": {
"message": "Edit service account",
"description": "Title for editing a service account."
},
"addProject": {
"message": "Add project",
@ -6037,8 +6039,8 @@
"hardDeleteSecretsConfirmation": {
"message": "Are you sure you want to permanently delete these secrets?"
},
"hardDeletesSuccessToast":{
"message":"Secrets permanently deleted"
"hardDeletesSuccessToast": {
"message": "Secrets permanently deleted"
},
"smAccess": {
"message": "Access",
@ -6052,7 +6054,7 @@
"message": "Service account name",
"description": "Label for the name of a service account"
},
"serviceAccountCreated": {
"serviceAccountCreated": {
"message": "Service account created",
"description": "Notifies that a new service account has been created"
},
@ -6140,8 +6142,8 @@
"message": "Secret sent to trash",
"description": "Notification to be displayed when a secret is successfully sent to the trash."
},
"hardDeleteSuccessToast":{
"message":"Secret permanently deleted"
"hardDeleteSuccessToast": {
"message": "Secret permanently deleted"
},
"accessTokens": {
"message": "Access tokens",
@ -6844,8 +6846,8 @@
"message": "with automatic enrollment will turn on when this option is used.",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The master password reset policy with automatic enrollment will turn on when this option is used.'"
},
"notFound":{
"message": "$RESOURCE$ not found",
"notFound": {
"message": "$RESOURCE$ not found",
"placeholders": {
"resource": {
"content": "$1",
@ -6998,6 +7000,17 @@
},
"freeOrganization": {
"message": "Free Organization"
},
"limitServiceAccounts": {
"message": "Limit service accounts (optional)"
},
"limitServiceAccountsDesc": {
"message": "Set a limit for your service accounts. Once this limit is reached, you will not be able to create new service accounts."
},
"serviceAccountLimit": {
"message": "Service account limit (optional)"
},
"maxServiceAccountCost": {
"message": "Max potential service account cost"
}
}

View File

@ -3,6 +3,7 @@ import { OrganizationSsoRequest } from "../../../auth/models/request/organizatio
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
import { ApiKeyResponse } from "../../../auth/models/response/api-key.response";
import { OrganizationSsoResponse } from "../../../auth/models/response/organization-sso.response";
import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models/request/organization-sm-subscription-update.request";
import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request";
import { OrganizationTaxInfoUpdateRequest } from "../../../billing/models/request/organization-tax-info-update.request";
import { PaymentRequest } from "../../../billing/models/request/payment.request";
@ -41,6 +42,10 @@ export class OrganizationApiServiceAbstraction {
id: string,
request: OrganizationSubscriptionUpdateRequest
) => Promise<void>;
updateSecretsManagerSubscription: (
id: string,
request: OrganizationSmSubscriptionUpdateRequest
) => Promise<void>;
updateSeats: (id: string, request: SeatRequest) => Promise<PaymentResponse>;
updateStorage: (id: string, request: StorageRequest) => Promise<PaymentResponse>;
verifyBank: (id: string, request: VerifyBankRequest) => Promise<void>;

View File

@ -28,6 +28,11 @@ export class OrganizationResponse extends BaseResponse {
useResetPassword: boolean;
useSecretsManager: boolean;
hasPublicAndPrivateKeys: boolean;
usePasswordManager: boolean;
smSeats?: number;
smServiceAccounts?: number;
maxAutoscaleSmSeats?: number;
maxAutoscaleSmServiceAccounts?: number;
constructor(response: any) {
super(response);
@ -62,5 +67,10 @@ export class OrganizationResponse extends BaseResponse {
this.useResetPassword = this.getResponseProperty("UseResetPassword");
this.useSecretsManager = this.getResponseProperty("UseSecretsManager");
this.hasPublicAndPrivateKeys = this.getResponseProperty("HasPublicAndPrivateKeys");
this.usePasswordManager = this.getResponseProperty("UsePasswordManager");
this.smSeats = this.getResponseProperty("SmSeats");
this.smServiceAccounts = this.getResponseProperty("SmServiceAccounts");
this.maxAutoscaleSmSeats = this.getResponseProperty("MaxAutoscaleSmSeats");
this.maxAutoscaleSmServiceAccounts = this.getResponseProperty("MaxAutoscaleSmServiceAccounts");
}
}

View File

@ -4,6 +4,7 @@ import { OrganizationSsoRequest } from "../../../auth/models/request/organizatio
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
import { ApiKeyResponse } from "../../../auth/models/response/api-key.response";
import { OrganizationSsoResponse } from "../../../auth/models/response/organization-sso.response";
import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models/request/organization-sm-subscription-update.request";
import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request";
import { OrganizationTaxInfoUpdateRequest } from "../../../billing/models/request/organization-tax-info-update.request";
import { PaymentRequest } from "../../../billing/models/request/payment.request";
@ -133,6 +134,19 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
);
}
async updateSecretsManagerSubscription(
id: string,
request: OrganizationSmSubscriptionUpdateRequest
): Promise<void> {
return this.apiService.send(
"POST",
"/organizations/" + id + "/sm-subscription",
request,
true,
false
);
}
async updateSeats(id: string, request: SeatRequest): Promise<PaymentResponse> {
const r = await this.apiService.send(
"POST",

View File

@ -1,4 +0,0 @@
export enum BitwardenProductType {
PasswordManager = 0,
SecretsManager = 1,
}

View File

@ -2,3 +2,4 @@ export * from "./payment-method-type.enum";
export * from "./plan-sponsorship-type.enum";
export * from "./plan-type.enum";
export * from "./transaction-type.enum";
export * from "./bitwarden-product-type.enum";

View File

@ -0,0 +1,40 @@
export class OrganizationSmSubscriptionUpdateRequest {
/**
* The number of seats to add or remove from the subscription.
*/
seatAdjustment: number;
/**
* The maximum number of seats that can be auto-scaled for the subscription.
*/
maxAutoscaleSeats?: number;
/**
* The number of additional service accounts to add or remove from the subscription.
*/
serviceAccountAdjustment: number;
/**
* The maximum number of additional service accounts that can be auto-scaled for the subscription.
*/
maxAutoscaleServiceAccounts?: number;
/**
* Build a subscription update request for the Secrets Manager product type.
* @param seatAdjustment - The number of seats to add or remove from the subscription.
* @param serviceAccountAdjustment - The number of additional service accounts to add or remove from the subscription.
* @param maxAutoscaleSeats - The maximum number of seats that can be auto-scaled for the subscription.
* @param maxAutoscaleServiceAccounts - The maximum number of additional service accounts that can be auto-scaled for the subscription.
*/
constructor(
seatAdjustment: number,
serviceAccountAdjustment: number,
maxAutoscaleSeats?: number,
maxAutoscaleServiceAccounts?: number
) {
this.seatAdjustment = seatAdjustment;
this.serviceAccountAdjustment = serviceAccountAdjustment;
this.maxAutoscaleSeats = maxAutoscaleSeats;
this.maxAutoscaleServiceAccounts = maxAutoscaleServiceAccounts;
}
}

View File

@ -1,3 +1,23 @@
export class OrganizationSubscriptionUpdateRequest {
constructor(public seatAdjustment: number, public maxAutoscaleSeats?: number) {}
/**
* The number of seats to add or remove from the subscription.
* Applies to both PM and SM request types.
*/
seatAdjustment: number;
/**
* The maximum number of seats that can be auto-scaled for the subscription.
* Applies to both PM and SM request types.
*/
maxAutoscaleSeats?: number;
/**
* Build a subscription update request for the Password Manager product type.
* @param seatAdjustment - The number of seats to add or remove from the subscription.
* @param maxAutoscaleSeats - The maximum number of seats that can be auto-scaled for the subscription.
*/
constructor(seatAdjustment: number, maxAutoscaleSeats?: number) {
this.seatAdjustment = seatAdjustment;
this.maxAutoscaleSeats = maxAutoscaleSeats;
}
}

View File

@ -1,7 +1,6 @@
import { ProductType } from "../../../enums";
import { BaseResponse } from "../../../models/response/base.response";
import { PlanType } from "../../enums";
import { BitwardenProductType } from "../../enums/bitwarden-product-type";
import { BitwardenProductType, PlanType } from "../../enums";
export class PlanResponse extends BaseResponse {
type: PlanType;

View File

@ -1,5 +1,5 @@
import { BaseResponse } from "../../../models/response/base.response";
import { BitwardenProductType } from "../../enums/bitwarden-product-type.enum";
import { BitwardenProductType } from "../../enums";
export class SubscriptionResponse extends BaseResponse {
storageName: string;