From bc65bf123823fa459f99e87d87f157bc558bdbed Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Thu, 3 Aug 2023 11:48:10 +0700 Subject: [PATCH] Add section for users to view purchased enterprise license keys in organization usage and billing section --- .../controllers/v1/organizationsController.ts | 20 ++++++ backend/src/ee/routes/v1/organizations.ts | 14 ++++ frontend/src/ee/components/ActivityTable.tsx | 3 +- frontend/src/hooks/api/organization/index.ts | 4 +- .../src/hooks/api/organization/queries.tsx | 23 ++++++- frontend/src/hooks/api/organization/types.ts | 11 +++ .../BillingCloudTab/CurrentPlanSection.tsx | 2 +- .../BillingSelfHostedTab.tsx | 9 +++ .../BillingSelfHostedTab/LicensesSection.tsx | 68 +++++++++++++++++++ .../components/BillingSelfHostedTab/index.tsx | 1 + .../BillingTabGroup/BillingTabGroup.tsx | 5 ++ 11 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/BillingSelfHostedTab.tsx create mode 100644 frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/LicensesSection.tsx create mode 100644 frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/index.tsx diff --git a/backend/src/ee/controllers/v1/organizationsController.ts b/backend/src/ee/controllers/v1/organizationsController.ts index 7f5432321..b473bcbd2 100644 --- a/backend/src/ee/controllers/v1/organizationsController.ts +++ b/backend/src/ee/controllers/v1/organizationsController.ts @@ -137,6 +137,12 @@ export const addOrganizationPmtMethod = async (req: Request, res: Response) => { }); } +/** + * Delete payment method with id [pmtMethodId] for organization + * @param req + * @param res + * @returns + */ export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => { const { pmtMethodId } = req.params; @@ -206,4 +212,18 @@ export const getOrganizationInvoices = async (req: Request, res: Response) => { ); return res.status(200).send(invoices); +} + +/** + * Return organization's licenses on file + * @param req + * @param res + * @returns + */ +export const getOrganizationLicenses = async (req: Request, res: Response) => { + const { data: { licenses } } = await licenseServerKeyRequest.get( + `${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/licenses` + ); + + return res.status(200).send(licenses); } \ No newline at end of file diff --git a/backend/src/ee/routes/v1/organizations.ts b/backend/src/ee/routes/v1/organizations.ts index c9104d964..5c308466d 100644 --- a/backend/src/ee/routes/v1/organizations.ts +++ b/backend/src/ee/routes/v1/organizations.ts @@ -220,4 +220,18 @@ router.get( organizationsController.getOrganizationInvoices ); +router.get( + "/:organizationId/licenses", + requireAuth({ + acceptedAuthModes: ["jwt"], + }), + requireOrganizationAuth({ + acceptedRoles: [OWNER, ADMIN], + acceptedStatuses: [ACCEPTED], + }), + param("organizationId").exists().trim(), + validateRequest, + organizationsController.getOrganizationLicenses +); + export default router; \ No newline at end of file diff --git a/frontend/src/ee/components/ActivityTable.tsx b/frontend/src/ee/components/ActivityTable.tsx index 185314791..b98f2dd7e 100644 --- a/frontend/src/ee/components/ActivityTable.tsx +++ b/frontend/src/ee/components/ActivityTable.tsx @@ -47,9 +47,10 @@ const ActivityLogsRow = ({ const { t } = useTranslation(); const renderUser = () => { + if (row?.user) return `${row.user}`; if (row?.serviceAccount) return `Service Account: ${row.serviceAccount.name}`; - if (row?.serviceTokenData.name) return `Service Token: ${row.serviceTokenData.name}`; + if (row?.serviceTokenData?.name) return `Service Token: ${row.serviceTokenData.name}`; return ""; }; diff --git a/frontend/src/hooks/api/organization/index.ts b/frontend/src/hooks/api/organization/index.ts index 847fb4bb9..ded8c3f29 100644 --- a/frontend/src/hooks/api/organization/index.ts +++ b/frontend/src/hooks/api/organization/index.ts @@ -7,6 +7,7 @@ export { useGetOrganization, useGetOrgBillingDetails, useGetOrgInvoices, + useGetOrgLicenses, useGetOrgPlanBillingInfo, useGetOrgPlansTable, useGetOrgPlanTable, @@ -14,5 +15,4 @@ export { useGetOrgTaxIds, useGetOrgTrialUrl, useRenameOrg, - useUpdateOrgBillingDetails -} from "./queries"; + useUpdateOrgBillingDetails} from "./queries"; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 63de2773d..66065c6ef 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -5,14 +5,14 @@ import { apiRequest } from "@app/config/request"; import { BillingDetails, Invoice, + License, Organization, OrgPlanTable, PlanBillingInfo, PmtMethod, ProductsTable, RenameOrgDTO, - TaxID -} from "./types"; + TaxID} from "./types"; const organizationKeys = { getUserOrganization: ["organization"] as const, @@ -23,6 +23,7 @@ const organizationKeys = { getOrgPmtMethods: (orgId: string) => [{ orgId }, "organization-pmt-methods"] as const, getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const, getOrgInvoices: (orgId: string) => [{ orgId }, "organization-invoices"] as const, + getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const }; export const useGetOrganization = () => { @@ -311,4 +312,20 @@ export const useCreateCustomerPortalSession = () => { return data; } }); -}; \ No newline at end of file +}; + +export const useGetOrgLicenses = (organizationId: string) => { + return useQuery({ + queryKey: organizationKeys.getOrgLicenses(organizationId), + queryFn: async () => { + if (organizationId === "") return undefined; + + const { data } = await apiRequest.get( + `/api/v1/organizations/${organizationId}/licenses` + ); + + return data; + }, + enabled: true + }); +} \ No newline at end of file diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index a33a7f875..cc4ad8e70 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -49,6 +49,17 @@ export type TaxID = { value: string; } +export type License = { + _id: string; + customerId: string; + prefix: string; + licenseKey: string; + isActivated: boolean; + expiresAt: string; + createdAt: string; + updatedAt: string; +} + export type OrgPlanTableHead = { name: string; } diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/CurrentPlanSection.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/CurrentPlanSection.tsx index e90d6ca8b..75fdf2f99 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/CurrentPlanSection.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/CurrentPlanSection.tsx @@ -33,7 +33,7 @@ export const CurrentPlanSection = () => { return (
-

Current Usage

+

Current usage

diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/BillingSelfHostedTab.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/BillingSelfHostedTab.tsx new file mode 100644 index 000000000..1ea4ca36b --- /dev/null +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/BillingSelfHostedTab.tsx @@ -0,0 +1,9 @@ +import { LicensesSection } from "./LicensesSection"; + +export const BillingSelfHostedTab = () => { + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/LicensesSection.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/LicensesSection.tsx new file mode 100644 index 000000000..1603d4486 --- /dev/null +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/LicensesSection.tsx @@ -0,0 +1,68 @@ +import { faFileContract } from "@fortawesome/free-solid-svg-icons"; + +import { + EmptyState, + Table, + TableContainer, + TableSkeleton, + TBody, + Td, + Th, + THead, + Tr +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { + useGetOrgLicenses +} from "@app/hooks/api"; + +export const LicensesSection = () => { + const { currentOrg } = useOrganization(); + const { data, isLoading } = useGetOrgLicenses(currentOrg?._id ?? ""); + + return ( +
+

Enterprise licenses

+ +
+ + + + + + + + + + {!isLoading && data && data?.length > 0 && data.map(({ + _id, + licenseKey, + isActivated, + createdAt, + expiresAt + }) => { + const formattedCreatedAt = new Date(createdAt).toISOString().split("T")[0]; + const formattedExpiresAt = new Date(expiresAt).toISOString().split("T")[0]; + return ( + + + + + + + ); + })} + {isLoading && } + {!isLoading && data && data?.length === 0 && ( + + + + )} + +
License KeyStatusIssued DateExpiry Date
{licenseKey}{isActivated ? "Active" : "Inactive"}{formattedCreatedAt}{formattedExpiresAt}
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/index.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/index.tsx new file mode 100644 index 000000000..3e443c2df --- /dev/null +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingSelfHostedTab/index.tsx @@ -0,0 +1 @@ +export * from "./BillingSelfHostedTab"; \ No newline at end of file diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingTabGroup/BillingTabGroup.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingTabGroup/BillingTabGroup.tsx index 417f06165..d30fa6ffa 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingTabGroup/BillingTabGroup.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingTabGroup/BillingTabGroup.tsx @@ -4,9 +4,11 @@ import { Tab } from "@headlessui/react" import { BillingCloudTab } from "../BillingCloudTab"; import { BillingDetailsTab } from "../BillingDetailsTab"; import { BillingReceiptsTab } from "../BillingReceiptsTab"; +import { BillingSelfHostedTab } from "../BillingSelfHostedTab"; const tabs = [ { name: "Infisical Cloud", key: "tab-infisical-cloud" }, + { name: "Infisical Self-Hosted", key: "tab-infisical-self-hosted" }, { name: "Receipts", key: "tab-receipts" }, { name: "Billing details", key: "tab-billing-details" } ]; @@ -32,6 +34,9 @@ export const BillingTabGroup = () => { + + +