mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Add section for users to view purchased enterprise license keys in organization usage and billing section
This commit is contained in:
@@ -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) => {
|
export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => {
|
||||||
const { pmtMethodId } = req.params;
|
const { pmtMethodId } = req.params;
|
||||||
|
|
||||||
@@ -206,4 +212,18 @@ export const getOrganizationInvoices = async (req: Request, res: Response) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send(invoices);
|
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);
|
||||||
}
|
}
|
||||||
@@ -220,4 +220,18 @@ router.get(
|
|||||||
organizationsController.getOrganizationInvoices
|
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;
|
export default router;
|
||||||
@@ -47,9 +47,10 @@ const ActivityLogsRow = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const renderUser = () => {
|
const renderUser = () => {
|
||||||
|
|
||||||
if (row?.user) return `${row.user}`;
|
if (row?.user) return `${row.user}`;
|
||||||
if (row?.serviceAccount) return `Service Account: ${row.serviceAccount.name}`;
|
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 "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export {
|
|||||||
useGetOrganization,
|
useGetOrganization,
|
||||||
useGetOrgBillingDetails,
|
useGetOrgBillingDetails,
|
||||||
useGetOrgInvoices,
|
useGetOrgInvoices,
|
||||||
|
useGetOrgLicenses,
|
||||||
useGetOrgPlanBillingInfo,
|
useGetOrgPlanBillingInfo,
|
||||||
useGetOrgPlansTable,
|
useGetOrgPlansTable,
|
||||||
useGetOrgPlanTable,
|
useGetOrgPlanTable,
|
||||||
@@ -14,5 +15,4 @@ export {
|
|||||||
useGetOrgTaxIds,
|
useGetOrgTaxIds,
|
||||||
useGetOrgTrialUrl,
|
useGetOrgTrialUrl,
|
||||||
useRenameOrg,
|
useRenameOrg,
|
||||||
useUpdateOrgBillingDetails
|
useUpdateOrgBillingDetails} from "./queries";
|
||||||
} from "./queries";
|
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import { apiRequest } from "@app/config/request";
|
|||||||
import {
|
import {
|
||||||
BillingDetails,
|
BillingDetails,
|
||||||
Invoice,
|
Invoice,
|
||||||
|
License,
|
||||||
Organization,
|
Organization,
|
||||||
OrgPlanTable,
|
OrgPlanTable,
|
||||||
PlanBillingInfo,
|
PlanBillingInfo,
|
||||||
PmtMethod,
|
PmtMethod,
|
||||||
ProductsTable,
|
ProductsTable,
|
||||||
RenameOrgDTO,
|
RenameOrgDTO,
|
||||||
TaxID
|
TaxID} from "./types";
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
const organizationKeys = {
|
const organizationKeys = {
|
||||||
getUserOrganization: ["organization"] as const,
|
getUserOrganization: ["organization"] as const,
|
||||||
@@ -23,6 +23,7 @@ const organizationKeys = {
|
|||||||
getOrgPmtMethods: (orgId: string) => [{ orgId }, "organization-pmt-methods"] as const,
|
getOrgPmtMethods: (orgId: string) => [{ orgId }, "organization-pmt-methods"] as const,
|
||||||
getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const,
|
getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const,
|
||||||
getOrgInvoices: (orgId: string) => [{ orgId }, "organization-invoices"] as const,
|
getOrgInvoices: (orgId: string) => [{ orgId }, "organization-invoices"] as const,
|
||||||
|
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetOrganization = () => {
|
export const useGetOrganization = () => {
|
||||||
@@ -311,4 +312,20 @@ export const useCreateCustomerPortalSession = () => {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetOrgLicenses = (organizationId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: organizationKeys.getOrgLicenses(organizationId),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (organizationId === "") return undefined;
|
||||||
|
|
||||||
|
const { data } = await apiRequest.get<License[]>(
|
||||||
|
`/api/v1/organizations/${organizationId}/licenses`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -49,6 +49,17 @@ export type TaxID = {
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type License = {
|
||||||
|
_id: string;
|
||||||
|
customerId: string;
|
||||||
|
prefix: string;
|
||||||
|
licenseKey: string;
|
||||||
|
isActivated: boolean;
|
||||||
|
expiresAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type OrgPlanTableHead = {
|
export type OrgPlanTableHead = {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const CurrentPlanSection = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<h2 className="mb-8 flex-1 text-xl font-semibold text-white">Current Usage</h2>
|
<h2 className="mb-8 flex-1 text-xl font-semibold text-white">Current usage</h2>
|
||||||
<TableContainer className="mt-4">
|
<TableContainer className="mt-4">
|
||||||
<Table>
|
<Table>
|
||||||
<THead>
|
<THead>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { LicensesSection } from "./LicensesSection";
|
||||||
|
|
||||||
|
export const BillingSelfHostedTab = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<LicensesSection />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<h2 className="mb-8 flex-1 text-xl font-semibold text-white">Enterprise licenses</h2>
|
||||||
|
<TableContainer className="mt-4">
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>License Key</Th>
|
||||||
|
<Th>Status</Th>
|
||||||
|
<Th>Issued Date</Th>
|
||||||
|
<Th>Expiry Date</Th>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{!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 (
|
||||||
|
<Tr key={`license-${_id}`} className="h-10">
|
||||||
|
<Td>{licenseKey}</Td>
|
||||||
|
<Td>{isActivated ? "Active" : "Inactive"}</Td>
|
||||||
|
<Td>{formattedCreatedAt}</Td>
|
||||||
|
<Td>{formattedExpiresAt}</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{isLoading && <TableSkeleton columns={4} innerKey="licenses" />}
|
||||||
|
{!isLoading && data && data?.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={4}>
|
||||||
|
<EmptyState title="No enterprise licenses on file" icon={faFileContract} />
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./BillingSelfHostedTab";
|
||||||
@@ -4,9 +4,11 @@ import { Tab } from "@headlessui/react"
|
|||||||
import { BillingCloudTab } from "../BillingCloudTab";
|
import { BillingCloudTab } from "../BillingCloudTab";
|
||||||
import { BillingDetailsTab } from "../BillingDetailsTab";
|
import { BillingDetailsTab } from "../BillingDetailsTab";
|
||||||
import { BillingReceiptsTab } from "../BillingReceiptsTab";
|
import { BillingReceiptsTab } from "../BillingReceiptsTab";
|
||||||
|
import { BillingSelfHostedTab } from "../BillingSelfHostedTab";
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: "Infisical Cloud", key: "tab-infisical-cloud" },
|
{ name: "Infisical Cloud", key: "tab-infisical-cloud" },
|
||||||
|
{ name: "Infisical Self-Hosted", key: "tab-infisical-self-hosted" },
|
||||||
{ name: "Receipts", key: "tab-receipts" },
|
{ name: "Receipts", key: "tab-receipts" },
|
||||||
{ name: "Billing details", key: "tab-billing-details" }
|
{ name: "Billing details", key: "tab-billing-details" }
|
||||||
];
|
];
|
||||||
@@ -32,6 +34,9 @@ export const BillingTabGroup = () => {
|
|||||||
<Tab.Panel>
|
<Tab.Panel>
|
||||||
<BillingCloudTab />
|
<BillingCloudTab />
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
|
<Tab.Panel>
|
||||||
|
<BillingSelfHostedTab />
|
||||||
|
</Tab.Panel>
|
||||||
<Tab.Panel>
|
<Tab.Panel>
|
||||||
<BillingReceiptsTab />
|
<BillingReceiptsTab />
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
|
|||||||
Reference in New Issue
Block a user