diff --git a/frontend/src/hooks/api/admin/index.ts b/frontend/src/hooks/api/admin/index.ts index 5405878c7..69f673187 100644 --- a/frontend/src/hooks/api/admin/index.ts +++ b/frontend/src/hooks/api/admin/index.ts @@ -1,7 +1,9 @@ export { useAdminDeleteUser, useCreateAdminUser, + useSetupInstanceGatewayConfig, useUpdateAdminSlackConfig, + useUpdateInstanceGatewayConfig, useUpdateServerConfig, useUpdateServerEncryptionStrategy } from "./mutation"; diff --git a/frontend/src/hooks/api/admin/mutation.ts b/frontend/src/hooks/api/admin/mutation.ts index c68760fa4..3bb1bfb2a 100644 --- a/frontend/src/hooks/api/admin/mutation.ts +++ b/frontend/src/hooks/api/admin/mutation.ts @@ -9,6 +9,7 @@ import { AdminSlackConfig, RootKeyEncryptionStrategy, TCreateAdminUserDTO, + TDisableInstanceGatewayConfigDTO, TServerConfig, TUpdateAdminSlackConfigDTO } from "./types"; @@ -98,3 +99,27 @@ export const useUpdateServerEncryptionStrategy = () => { } }); }; + +export const useSetupInstanceGatewayConfig = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async () => { + await apiRequest.post("/api/v1/admin/gateway"); + }, + onSuccess: () => { + queryClient.invalidateQueries(adminQueryKeys.getInstanceGatewayConfig()); + } + }); +}; + +export const useUpdateInstanceGatewayConfig = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (dto: TDisableInstanceGatewayConfigDTO) => { + await apiRequest.patch("/api/v1/admin/gateway", dto); + }, + onSuccess: () => { + queryClient.invalidateQueries(adminQueryKeys.getInstanceGatewayConfig()); + } + }); +}; diff --git a/frontend/src/hooks/api/admin/queries.ts b/frontend/src/hooks/api/admin/queries.ts index 12ae1cf64..afcbd31fb 100644 --- a/frontend/src/hooks/api/admin/queries.ts +++ b/frontend/src/hooks/api/admin/queries.ts @@ -1,4 +1,5 @@ -import { useInfiniteQuery, useQuery, UseQueryOptions } from "@tanstack/react-query"; +import { queryOptions, useInfiniteQuery, useQuery, UseQueryOptions } from "@tanstack/react-query"; +import { AxiosError } from "axios"; import { apiRequest } from "@app/config/request"; @@ -7,6 +8,7 @@ import { AdminGetUsersFilters, AdminSlackConfig, TGetServerRootKmsEncryptionDetails, + TInstanceGatewayConfig, TServerConfig } from "./types"; @@ -18,7 +20,21 @@ export const adminQueryKeys = { serverConfig: () => ["server-config"] as const, getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const, getAdminSlackConfig: () => ["admin-slack-config"] as const, - getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const + getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const, + + getInstanceGatewayConfig: () => + queryOptions({ + queryKey: ["instance-gateway-config"], + queryFn: async () => { + const { data } = await apiRequest + .get<{ gateway: TInstanceGatewayConfig }>("/api/v1/admin/gateway") + .catch((err: AxiosError) => { + if (err.response?.status === 404) return { data: { gateway: false as const } }; + throw err; + }); + return data.gateway; + } + }) }; export const fetchServerConfig = async () => { diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index 60fa3ab98..282af07a2 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -66,3 +66,14 @@ export enum RootKeyEncryptionStrategy { Software = "SOFTWARE", HSM = "HSM" } + +export type TInstanceGatewayConfig = { + isDisabled: boolean; + infisicalClientCaIssuedAt: string; + infisicalClientCaSerialNumber: string; + caKeyAlgorithm: string; +}; + +export type TDisableInstanceGatewayConfigDTO = { + isDisabled?: boolean; +}; diff --git a/frontend/src/hooks/api/subscriptions/types.ts b/frontend/src/hooks/api/subscriptions/types.ts index 3129dd508..b6b653ece 100644 --- a/frontend/src/hooks/api/subscriptions/types.ts +++ b/frontend/src/hooks/api/subscriptions/types.ts @@ -42,6 +42,7 @@ export type SubscriptionPlan = { has_used_trial: boolean; caCrl: boolean; instanceUserManagement: boolean; + gateway: boolean; externalKms: boolean; pkiEst: boolean; enforceMfa: boolean; diff --git a/frontend/src/pages/admin/OverviewPage/GatewayPanel.tsx b/frontend/src/pages/admin/OverviewPage/GatewayPanel.tsx new file mode 100644 index 000000000..8590bee47 --- /dev/null +++ b/frontend/src/pages/admin/OverviewPage/GatewayPanel.tsx @@ -0,0 +1,114 @@ +import { useQuery } from "@tanstack/react-query"; +import { format } from "date-fns"; + +import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; +import { createNotification } from "@app/components/notifications"; +import { Button, EmptyState, Tooltip } from "@app/components/v2"; +import { useSubscription } from "@app/context"; +import { usePopUp } from "@app/hooks"; +import { useSetupInstanceGatewayConfig, useUpdateInstanceGatewayConfig } from "@app/hooks/api"; +import { adminQueryKeys } from "@app/hooks/api/admin/queries"; + +export const GatewayPanel = () => { + const { subscription } = useSubscription(); + const { data } = useQuery(adminQueryKeys.getInstanceGatewayConfig()); + const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["upgradePlan"] as const); + + const setupInstanceConfig = useSetupInstanceGatewayConfig(); + const updateInstanceConfig = useUpdateInstanceGatewayConfig(); + + const handleSetupInstance = () => { + if (!subscription.gateway) { + handlePopUpOpen("upgradePlan"); + return; + } + + if (!setupInstanceConfig.isPending) { + setupInstanceConfig.mutate(undefined, { + onSuccess: () => { + createNotification({ + type: "success", + text: "Instance gateway setup completed" + }); + } + }); + } + }; + + const handleUpdateGatewayStatus = ({ isDisabled }: { isDisabled?: boolean }) => { + if (!updateInstanceConfig.isPending) { + updateInstanceConfig.mutate( + { isDisabled }, + { + onSuccess: () => { + createNotification({ + type: "success", + text: "Instance gateway updated" + }); + } + } + ); + } + }; + + if (!data) { + return ( +