Show usage and billing for self-hosted instances

This commit is contained in:
carlosmonastyrski
2025-03-21 13:57:45 -03:00
parent 351e573fea
commit 9924ef3a71
7 changed files with 171 additions and 60 deletions
@@ -0,0 +1,24 @@
export const BillingPlanRows = {
MemberLimit: { name: "Organization member limit", field: "memberLimit" },
IdentityLimit: { name: "Organization identity limit", field: "identityLimit" },
WorkspaceLimit: { name: "Project limit", field: "workspaceLimit" },
EnvironmentLimit: { name: "Environment limit", field: "environmentLimit" },
SecretVersioning: { name: "Secret versioning", field: "secretVersioning" },
PitRecovery: { name: "Point in time recovery", field: "pitRecovery" },
Rbac: { name: "RBAC", field: "rbac" },
CustomRateLimits: { name: "Custom rate limits", field: "customRateLimits" },
CustomAlerts: { name: "Custom alerts", field: "customAlerts" },
AuditLogs: { name: "Audit logs", field: "auditLogs" },
SamlSSO: { name: "SAML SSO", field: "samlSSO" },
Hsm: { name: "Hardware Security Module (HSM)", field: "hsm" },
OidcSSO: { name: "OIDC SSO", field: "oidcSSO" },
SecretApproval: { name: "Secret approvals", field: "secretApproval" },
SecretRotation: { name: "Secret rotation", field: "secretRotation" },
InstanceUserManagement: { name: "Instance User Management", field: "instanceUserManagement" },
ExternalKms: { name: "External KMS", field: "externalKms" }
} as const;
export const BillingPlanTableHead = {
Allowed: { name: "Allowed" },
Used: { name: "Used" }
} as const;
@@ -12,10 +12,13 @@ import { getConfig } from "@app/lib/config/env";
import { verifyOfflineLicense } from "@app/lib/crypto";
import { NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TIdentityOrgDALFactory } from "@app/services/identity/identity-org-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums";
import { TLicenseDALFactory } from "./license-dal";
import { getDefaultOnPremFeatures, setupLicenseRequestWithStore } from "./license-fns";
import {
@@ -28,6 +31,7 @@ import {
TFeatureSet,
TGetOrgBillInfoDTO,
TGetOrgTaxIdDTO,
TOfflineLicense,
TOfflineLicenseContents,
TOrgInvoiceDTO,
TOrgLicensesDTO,
@@ -39,10 +43,12 @@ import {
} from "./license-types";
type TLicenseServiceFactoryDep = {
orgDAL: Pick<TOrgDALFactory, "findOrgById">;
orgDAL: Pick<TOrgDALFactory, "findOrgById" | "countAllOrgMembers">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseDAL: TLicenseDALFactory;
keyStore: Pick<TKeyStoreFactory, "setItemWithExpiry" | "getItem" | "deleteItem">;
identityOrgMembershipDAL: TIdentityOrgDALFactory;
projectDAL: TProjectDALFactory;
};
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
@@ -57,11 +63,14 @@ export const licenseServiceFactory = ({
orgDAL,
permissionService,
licenseDAL,
keyStore
keyStore,
identityOrgMembershipDAL,
projectDAL
}: TLicenseServiceFactoryDep) => {
let isValidLicense = false;
let instanceType = InstanceType.OnPrem;
let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures();
let selfHostedLicense: TOfflineLicense | null = null;
const appCfg = getConfig();
const licenseServerCloudApi = setupLicenseRequestWithStore(
@@ -125,6 +134,7 @@ export const licenseServiceFactory = ({
instanceType = InstanceType.EnterpriseOnPremOffline;
logger.info(`Instance type: ${InstanceType.EnterpriseOnPremOffline}`);
isValidLicense = true;
selfHostedLicense = contents.license;
return;
}
}
@@ -348,10 +358,21 @@ export const licenseServiceFactory = ({
message: `Organization with ID '${orgId}' not found`
});
}
const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/billing`
);
return data;
if (instanceType !== InstanceType.OnPrem && instanceType !== InstanceType.EnterpriseOnPremOffline) {
const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/billing`
);
return data;
}
return {
currentPeriodStart: selfHostedLicense?.issuedAt ? Date.parse(selfHostedLicense?.issuedAt) / 1000 : undefined,
currentPeriodEnd: selfHostedLicense?.expiresAt ? Date.parse(selfHostedLicense?.expiresAt) / 1000 : undefined,
interval: "month",
intervalCount: 1,
amount: 0,
quantity: 1
};
};
// returns org current plan feature table
@@ -365,10 +386,41 @@ export const licenseServiceFactory = ({
message: `Organization with ID '${orgId}' not found`
});
}
const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/table`
if (instanceType !== InstanceType.OnPrem && instanceType !== InstanceType.EnterpriseOnPremOffline) {
const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/table`
);
return data;
}
const mappedRows = await Promise.all(
Object.values(BillingPlanRows).map(async ({ name, field }: { name: string; field: string }) => {
const allowed = onPremFeatures[field as keyof TFeatureSet];
let used = "-";
if (field === BillingPlanRows.MemberLimit.field) {
const orgMemberships = await orgDAL.countAllOrgMembers(orgId);
used = orgMemberships.toString();
} else if (field === BillingPlanRows.WorkspaceLimit.field) {
const projects = await projectDAL.find({ orgId });
used = projects.length.toString();
} else if (field === BillingPlanRows.IdentityLimit.field) {
const identities = await identityOrgMembershipDAL.countAllOrgIdentities({ orgId });
used = identities.toString();
}
return {
name,
allowed,
used
};
})
);
return data;
return {
head: Object.values(BillingPlanTableHead),
rows: mappedRows
};
};
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
+8 -1
View File
@@ -413,7 +413,14 @@ export const registerRoutes = async (
serviceTokenDAL,
projectDAL
});
const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore });
const licenseService = licenseServiceFactory({
permissionService,
orgDAL,
licenseDAL,
keyStore,
identityOrgMembershipDAL,
projectDAL
});
const hsmService = hsmServiceFactory({
hsmModule,