mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 16:27:46 +00:00
feat(webhook): implemented ui for webhooks
This commit is contained in:
Generated
+11
@@ -44,6 +44,7 @@
|
|||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"cookies": "^0.8.0",
|
"cookies": "^0.8.0",
|
||||||
"cva": "npm:class-variance-authority@^0.4.0",
|
"cva": "npm:class-variance-authority@^0.4.0",
|
||||||
|
"dayjs": "^1.11.9",
|
||||||
"framer-motion": "^6.2.3",
|
"framer-motion": "^6.2.3",
|
||||||
"fs": "^0.0.2",
|
"fs": "^0.0.2",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
@@ -10571,6 +10572,11 @@
|
|||||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz",
|
||||||
|
"integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
@@ -30438,6 +30444,11 @@
|
|||||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"version": "1.11.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz",
|
||||||
|
"integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"cookies": "^0.8.0",
|
"cookies": "^0.8.0",
|
||||||
"cva": "npm:class-variance-authority@^0.4.0",
|
"cva": "npm:class-variance-authority@^0.4.0",
|
||||||
|
"dayjs": "^1.11.9",
|
||||||
"framer-motion": "^6.2.3",
|
"framer-motion": "^6.2.3",
|
||||||
"fs": "^0.0.2",
|
"fs": "^0.0.2",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
|||||||
@@ -251,6 +251,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"webhooks": {
|
||||||
|
"title": "Webhooks",
|
||||||
|
"description": "Manage webhooks to setup deployment hooks for your various integrations."
|
||||||
|
},
|
||||||
"members": {
|
"members": {
|
||||||
"title": "Project Members",
|
"title": "Project Members",
|
||||||
"description": "This page shows the members of the selected project, and allows you to modify their permissions."
|
"description": "This page shows the members of the selected project, and allows you to modify their permissions."
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ const buttonVariants = cva(
|
|||||||
{
|
{
|
||||||
colorSchema: "primary",
|
colorSchema: "primary",
|
||||||
variant: "star",
|
variant: "star",
|
||||||
className: "bg-mineshaft-700 border border-mineshaft-600 hover:bg-primary hover:text-black hover:border-primary-400 duration-100"
|
className:
|
||||||
|
"bg-mineshaft-700 border border-mineshaft-600 hover:bg-primary hover:text-black hover:border-primary-400 duration-100"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
colorSchema: "primary",
|
colorSchema: "primary",
|
||||||
@@ -76,12 +77,14 @@ const buttonVariants = cva(
|
|||||||
{
|
{
|
||||||
colorSchema: "primary",
|
colorSchema: "primary",
|
||||||
variant: "outline_bg",
|
variant: "outline_bg",
|
||||||
className: "bg-mineshaft-600 border border-mineshaft-500 hover:bg-primary/[0.1] hover:border-primary/40 text-bunker-200"
|
className:
|
||||||
|
"bg-mineshaft-600 border border-mineshaft-500 hover:bg-primary/[0.1] hover:border-primary/40 text-bunker-200"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
colorSchema: "secondary",
|
colorSchema: "secondary",
|
||||||
variant: "star",
|
variant: "star",
|
||||||
className: "bg-mineshaft-700 border border-mineshaft-600 hover:bg-mineshaft hover:text-white"
|
className:
|
||||||
|
"bg-mineshaft-700 border border-mineshaft-600 hover:bg-mineshaft hover:text-white"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
colorSchema: "danger",
|
colorSchema: "danger",
|
||||||
@@ -163,13 +166,13 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
type="button"
|
type="button"
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
buttonVariants({
|
buttonVariants({
|
||||||
className,
|
|
||||||
colorSchema,
|
colorSchema,
|
||||||
size,
|
size,
|
||||||
variant,
|
variant,
|
||||||
isRounded,
|
isRounded,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isFullWidth
|
isFullWidth,
|
||||||
|
className
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
@@ -193,7 +196,15 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
>
|
>
|
||||||
{leftIcon}
|
{leftIcon}
|
||||||
</div>
|
</div>
|
||||||
<span className={twMerge("transition-all", isFullWidth ? "w-full" : "w-min", loadingToggleClass)}>{children}</span>
|
<span
|
||||||
|
className={twMerge(
|
||||||
|
"transition-all",
|
||||||
|
isFullWidth ? "w-full" : "w-min",
|
||||||
|
loadingToggleClass
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"inline-flex shrink-0 cursor-pointer items-center justify-center transition-all",
|
"inline-flex shrink-0 cursor-pointer items-center justify-center transition-all",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ReactNode } from "react";
|
|||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
export type TooltipProps = {
|
export type TooltipProps = Omit<TooltipPrimitive.TooltipContentProps, "open" | "content"> & {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
content?: ReactNode;
|
content?: ReactNode;
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
@@ -10,7 +10,7 @@ export type TooltipProps = {
|
|||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
onOpenChange?: (isOpen: boolean) => void;
|
onOpenChange?: (isOpen: boolean) => void;
|
||||||
defaultOpen?: boolean;
|
defaultOpen?: boolean;
|
||||||
} & Omit<TooltipPrimitive.TooltipContentProps, "open">;
|
};
|
||||||
|
|
||||||
export const Tooltip = ({
|
export const Tooltip = ({
|
||||||
children,
|
children,
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ export * from "./serviceTokens";
|
|||||||
export * from "./subscriptions";
|
export * from "./subscriptions";
|
||||||
export * from "./tags";
|
export * from "./tags";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
|
export * from "./webhooks";
|
||||||
export * from "./workspace";
|
export * from "./workspace";
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type { CreateServiceTokenDTO, ServiceToken } from "./serviceTokens/types"
|
|||||||
export type { SubscriptionPlan } from "./subscriptions/types";
|
export type { SubscriptionPlan } from "./subscriptions/types";
|
||||||
export type { WsTag } from "./tags/types";
|
export type { WsTag } from "./tags/types";
|
||||||
export type { AddUserToWsDTO, AddUserToWsRes, OrgUser, User } from "./users/types";
|
export type { AddUserToWsDTO, AddUserToWsRes, OrgUser, User } from "./users/types";
|
||||||
|
export type { TWebhook } from "./webhooks/types";
|
||||||
export type {
|
export type {
|
||||||
CreateEnvironmentDTO,
|
CreateEnvironmentDTO,
|
||||||
CreateWorkspaceDTO,
|
CreateWorkspaceDTO,
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { useCreateWebhook, useDeleteWebhook, useTestWebhook, useUpdateWebhook } from "./mutation";
|
||||||
|
export { useGetWebhooks } from "./query";
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { queryKeys } from "./query";
|
||||||
|
import { TCreateWebhookDto, TDeleteWebhookDto, TTestWebhookDTO, TUpdateWebhookDto } from "./types";
|
||||||
|
|
||||||
|
export const useCreateWebhook = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, TCreateWebhookDto>({
|
||||||
|
mutationFn: async (dto) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/webhooks", dto);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { workspaceId }) => {
|
||||||
|
queryClient.invalidateQueries(queryKeys.getWebhooks(workspaceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTestWebhook = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, TTestWebhookDTO>({
|
||||||
|
mutationFn: async ({ webhookId }) => {
|
||||||
|
const { data } = await apiRequest.post(`/api/v1/webhooks/${webhookId}/test`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { workspaceId }) => {
|
||||||
|
queryClient.invalidateQueries(queryKeys.getWebhooks(workspaceId));
|
||||||
|
},
|
||||||
|
onError: (_, { workspaceId }) => {
|
||||||
|
queryClient.invalidateQueries(queryKeys.getWebhooks(workspaceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateWebhook = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, TUpdateWebhookDto>({
|
||||||
|
mutationFn: async (dto) => {
|
||||||
|
const { data } = await apiRequest.patch(`/api/v1/webhooks/${dto.webhookId}`, {
|
||||||
|
isDisabled: dto.isDisabled
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { workspaceId }) => {
|
||||||
|
queryClient.invalidateQueries(queryKeys.getWebhooks(workspaceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteWebhook = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, TDeleteWebhookDto>({
|
||||||
|
mutationFn: async (dto) => {
|
||||||
|
const { data } = await apiRequest.delete(`/api/v1/webhooks/${dto.webhookId}`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { workspaceId }) => {
|
||||||
|
queryClient.invalidateQueries(queryKeys.getWebhooks(workspaceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { TWebhook } from "./types";
|
||||||
|
|
||||||
|
export const queryKeys = {
|
||||||
|
getWebhooks: (workspaceId: string) => ["webhooks", { workspaceId }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchWebhooks = async (workspaceId: string) => {
|
||||||
|
const { data } = await apiRequest.get<{ webhooks: TWebhook[] }>("/api/v1/webhooks", {
|
||||||
|
params: {
|
||||||
|
workspaceId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.webhooks;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetWebhooks = (workspaceId: string) =>
|
||||||
|
useQuery({
|
||||||
|
queryKey: queryKeys.getWebhooks(workspaceId),
|
||||||
|
queryFn: () => fetchWebhooks(workspaceId),
|
||||||
|
enabled: Boolean(workspaceId)
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export type TWebhook = {
|
||||||
|
_id: string;
|
||||||
|
workspace: string;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
url: string;
|
||||||
|
lastStatus: "success" | "failed";
|
||||||
|
lastRunErrorMessage?: string;
|
||||||
|
isDisabled: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TCreateWebhookDto = {
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
webhookUrl: string;
|
||||||
|
webhookSecretKey?: string;
|
||||||
|
secretPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUpdateWebhookDto = {
|
||||||
|
webhookId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteWebhookDto = {
|
||||||
|
webhookId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTestWebhookDTO = {
|
||||||
|
webhookId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
@@ -13,7 +13,18 @@ import { useTranslation } from "react-i18next";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||||
import { faAngleDown, faArrowLeft, faArrowUpRightFromSquare, faBook, faCheck, faEnvelope, faInfinity, faMobile, faPlus, faQuestion } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faAngleDown,
|
||||||
|
faArrowLeft,
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
|
faBook,
|
||||||
|
faCheck,
|
||||||
|
faEnvelope,
|
||||||
|
faInfinity,
|
||||||
|
faMobile,
|
||||||
|
faPlus,
|
||||||
|
faQuestion
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
|
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
|
||||||
@@ -41,7 +52,14 @@ import {
|
|||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useOrganization, useSubscription, useUser, useWorkspace } from "@app/context";
|
import { useOrganization, useSubscription, useUser, useWorkspace } from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useGetOrgTrialUrl, useLogoutUser, useUploadWsKey } from "@app/hooks/api";
|
import {
|
||||||
|
fetchOrgUsers,
|
||||||
|
useAddUserToWs,
|
||||||
|
useCreateWorkspace,
|
||||||
|
useGetOrgTrialUrl,
|
||||||
|
useLogoutUser,
|
||||||
|
useUploadWsKey
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -89,7 +107,9 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
// const [ isLearningNoteOpen, setIsLearningNoteOpen ] = useState(true);
|
// const [ isLearningNoteOpen, setIsLearningNoteOpen ] = useState(true);
|
||||||
|
|
||||||
const isAddingProjectsAllowed = subscription?.workspaceLimit ? (subscription.workspacesUsed < subscription.workspaceLimit) : true;
|
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
||||||
|
? subscription.workspacesUsed < subscription.workspaceLimit
|
||||||
|
: true;
|
||||||
|
|
||||||
const createWs = useCreateWorkspace();
|
const createWs = useCreateWorkspace();
|
||||||
const uploadWsKey = useUploadWsKey();
|
const uploadWsKey = useUploadWsKey();
|
||||||
@@ -110,22 +130,22 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleRouteChange = () => {
|
const handleRouteChange = () => {
|
||||||
(window).Intercom("update");
|
window.Intercom("update");
|
||||||
};
|
};
|
||||||
|
|
||||||
router.events.on("routeChangeComplete", handleRouteChange);
|
router.events.on("routeChangeComplete", handleRouteChange);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
router.events.off("routeChangeComplete", handleRouteChange);
|
router.events.off("routeChangeComplete", handleRouteChange);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const logout = useLogoutUser();
|
const logout = useLogoutUser();
|
||||||
const logOutUser = async () => {
|
const logOutUser = async () => {
|
||||||
try {
|
try {
|
||||||
console.log("Logging out...")
|
console.log("Logging out...");
|
||||||
await logout.mutateAsync();
|
await logout.mutateAsync();
|
||||||
localStorage.removeItem("protectedKey");
|
localStorage.removeItem("protectedKey");
|
||||||
localStorage.removeItem("protectedKeyIV");
|
localStorage.removeItem("protectedKeyIV");
|
||||||
@@ -145,27 +165,30 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
|
|
||||||
const changeOrg = async (orgId) => {
|
const changeOrg = async (orgId) => {
|
||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
router.push(`/org/${orgId}/overview`)
|
router.push(`/org/${orgId}/overview`);
|
||||||
}
|
};
|
||||||
|
|
||||||
// TODO(akhilmhdh): This entire logic will be rechecked and will try to avoid
|
// TODO(akhilmhdh): This entire logic will be rechecked and will try to avoid
|
||||||
// Placing the localstorage as much as possible
|
// Placing the localstorage as much as possible
|
||||||
// Wait till tony integrates the azure and its launched
|
// Wait till tony integrates the azure and its launched
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
// Put a user in an org if they're not in one yet
|
// Put a user in an org if they're not in one yet
|
||||||
const putUserInOrg = async () => {
|
const putUserInOrg = async () => {
|
||||||
if (tempLocalStorage("orgData.id") === "") {
|
if (tempLocalStorage("orgData.id") === "") {
|
||||||
localStorage.setItem("orgData.id", orgs[0]?._id);
|
localStorage.setItem("orgData.id", orgs[0]?._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentOrg && (
|
if (
|
||||||
(workspaces?.length === 0 && router.asPath.includes("project"))
|
currentOrg &&
|
||||||
|| router.asPath.includes("/project/undefined")
|
((workspaces?.length === 0 && router.asPath.includes("project")) ||
|
||||||
|| (!orgs?.map(org => org._id)?.includes(router.query.id) && !router.asPath.includes("project") && !router.asPath.includes("personal") && !router.asPath.includes("integration"))
|
router.asPath.includes("/project/undefined") ||
|
||||||
)) {
|
(!orgs?.map((org) => org._id)?.includes(router.query.id) &&
|
||||||
|
!router.asPath.includes("project") &&
|
||||||
|
!router.asPath.includes("personal") &&
|
||||||
|
!router.asPath.includes("integration")))
|
||||||
|
) {
|
||||||
router.push(`/org/${currentOrg?._id}/overview`);
|
router.push(`/org/${currentOrg?._id}/overview`);
|
||||||
}
|
}
|
||||||
// else if (!router.asPath.includes("org") && !router.asPath.includes("project") && !router.asPath.includes("integrations") && !router.asPath.includes("personal-settings")) {
|
// else if (!router.asPath.includes("org") && !router.asPath.includes("project") && !router.asPath.includes("integrations") && !router.asPath.includes("personal-settings")) {
|
||||||
|
|
||||||
// const pathSegments = router.asPath.split("/").filter((segment) => segment.length > 0);
|
// const pathSegments = router.asPath.split("/").filter((segment) => segment.length > 0);
|
||||||
@@ -244,134 +267,171 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
<>
|
<>
|
||||||
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
|
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
|
||||||
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
||||||
<aside className="w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60 dark">
|
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
|
||||||
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
||||||
<div>
|
<div>
|
||||||
{!router.asPath.includes("personal") && <div className="h-12 px-3 flex items-center pt-6 cursor-default">
|
{!router.asPath.includes("personal") && (
|
||||||
{(router.asPath.includes("project") || router.asPath.includes("integrations")) && <Link href={`/org/${currentOrg?._id}/overview`}><div className="pl-1 pr-2 text-mineshaft-400 hover:text-mineshaft-100 duration-200">
|
<div className="flex h-12 cursor-default items-center px-3 pt-6">
|
||||||
<FontAwesomeIcon icon={faArrowLeft} />
|
{(router.asPath.includes("project") ||
|
||||||
</div></Link>}
|
router.asPath.includes("integrations")) && (
|
||||||
<DropdownMenu>
|
<Link href={`/org/${currentOrg?._id}/overview`}>
|
||||||
<DropdownMenuTrigger asChild className="data-[state=open]:bg-mineshaft-600">
|
<div className="pl-1 pr-2 text-mineshaft-400 duration-200 hover:text-mineshaft-100">
|
||||||
<div className="mr-auto flex items-center hover:bg-mineshaft-600 py-1.5 pl-1.5 pr-2 rounded-md">
|
<FontAwesomeIcon icon={faArrowLeft} />
|
||||||
<div className="w-5 h-5 rounded-md bg-primary flex justify-center items-center text-sm">{currentOrg?.name.charAt(0)}</div>
|
</div>
|
||||||
<div className="pl-3 text-mineshaft-100 text-sm">{currentOrg?.name} <FontAwesomeIcon icon={faAngleDown} className="text-xs pl-1 pt-1 text-mineshaft-300" /></div>
|
</Link>
|
||||||
</div>
|
)}
|
||||||
</DropdownMenuTrigger>
|
<DropdownMenu>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuTrigger asChild className="data-[state=open]:bg-mineshaft-600">
|
||||||
<div className="text-xs text-mineshaft-400 px-2 py-1">{user?.email}</div>
|
<div className="mr-auto flex items-center rounded-md py-1.5 pl-1.5 pr-2 hover:bg-mineshaft-600">
|
||||||
{orgs?.map(org => <DropdownMenuItem key={org._id}>
|
<div className="flex h-5 w-5 items-center justify-center rounded-md bg-primary text-sm">
|
||||||
<Button
|
{currentOrg?.name.charAt(0)}
|
||||||
onClick={() => changeOrg(org?._id)}
|
</div>
|
||||||
variant="plain"
|
<div className="pl-3 text-sm text-mineshaft-100">
|
||||||
colorSchema="secondary"
|
{currentOrg?.name}{" "}
|
||||||
size="xs"
|
<FontAwesomeIcon
|
||||||
className="w-full flex items-center justify-start p-0 font-normal"
|
icon={faAngleDown}
|
||||||
leftIcon={currentOrg._id === org._id && <FontAwesomeIcon icon={faCheck} className="mr-3 text-primary"/>}
|
className="pl-1 pt-1 text-xs text-mineshaft-300"
|
||||||
>
|
/>
|
||||||
<div className="w-full flex justify-between items-center">{org.name}</div>
|
</div>
|
||||||
</Button>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuTrigger>
|
||||||
)}
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
<div className="h-1 mt-1 border-t border-mineshaft-600"/>
|
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.email}</div>
|
||||||
<button
|
{orgs?.map((org) => (
|
||||||
type="button"
|
<DropdownMenuItem key={org._id}>
|
||||||
onClick={logOutUser}
|
<Button
|
||||||
className="w-full"
|
onClick={() => changeOrg(org?._id)}
|
||||||
>
|
variant="plain"
|
||||||
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
colorSchema="secondary"
|
||||||
</button>
|
size="xs"
|
||||||
</DropdownMenuContent>
|
className="flex w-full items-center justify-start p-0 font-normal"
|
||||||
</DropdownMenu>
|
leftIcon={
|
||||||
<DropdownMenu>
|
currentOrg._id === org._id && (
|
||||||
<DropdownMenuTrigger asChild className="hover:bg-primary-400 hover:text-black data-[state=open]:text-black data-[state=open]:bg-primary-400 p-1">
|
<FontAwesomeIcon icon={faCheck} className="mr-3 text-primary" />
|
||||||
<div className="child w-6 h-6 rounded-full bg-mineshaft hover:bg-mineshaft-500 pr-1 text-xs text-mineshaft-300 flex justify-center items-center">
|
)
|
||||||
{user?.firstName?.charAt(0)}{user?.lastName && user?.lastName?.charAt(0)}
|
}
|
||||||
</div>
|
>
|
||||||
</DropdownMenuTrigger>
|
<div className="flex w-full items-center justify-between">
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
{org.name}
|
||||||
<div className="text-xs text-mineshaft-400 px-2 py-1">{user?.email}</div>
|
</div>
|
||||||
<Link href="/personal-settings"><DropdownMenuItem>Personal Settings</DropdownMenuItem></Link>
|
</Button>
|
||||||
<a
|
</DropdownMenuItem>
|
||||||
href="https://infisical.com/docs/documentation/getting-started/introduction"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="w-full mt-3 text-sm text-mineshaft-300 font-normal leading-[1.2rem] hover:text-mineshaft-100"
|
|
||||||
>
|
|
||||||
<DropdownMenuItem>Documentation<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="pl-1.5 text-xxs mb-[0.06rem]" /></DropdownMenuItem>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://join.slack.com/t/infisical-users/shared_invite/zt-1ye0tm8ab-899qZ6ZbpfESuo6TEikyOQ"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="w-full mt-3 text-sm text-mineshaft-300 font-normal leading-[1.2rem] hover:text-mineshaft-100"
|
|
||||||
>
|
|
||||||
<DropdownMenuItem>Join Slack Community<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="pl-1.5 text-xxs mb-[0.06rem]" /></DropdownMenuItem>
|
|
||||||
</a>
|
|
||||||
<div className="h-1 mt-1 border-t border-mineshaft-600"/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={logOutUser}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
|
||||||
</button>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>}
|
|
||||||
{!router.asPath.includes("org") && (!router.asPath.includes("personal") && currentWorkspace ? (
|
|
||||||
<div className="mt-5 mb-4 w-full p-3">
|
|
||||||
<p className="ml-1.5 mb-1 text-xs font-semibold uppercase text-gray-400">
|
|
||||||
Project
|
|
||||||
</p>
|
|
||||||
<Select
|
|
||||||
defaultValue={currentWorkspace?._id}
|
|
||||||
value={currentWorkspace?._id}
|
|
||||||
className="w-full truncate bg-mineshaft-600 py-2.5 font-medium"
|
|
||||||
onValueChange={(value) => {
|
|
||||||
router.push(`/project/${value}/secrets`);
|
|
||||||
}}
|
|
||||||
position="popper"
|
|
||||||
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 z-50 max-h-96 border-gray-700"
|
|
||||||
>
|
|
||||||
<div className='h-full no-scrollbar no-scrollbar::-webkit-scrollbar'>
|
|
||||||
{workspaces
|
|
||||||
.filter((ws) => ws.organization === currentOrg?._id)
|
|
||||||
.map(({ _id, name }) => (
|
|
||||||
<SelectItem
|
|
||||||
key={`ws-layout-list-${_id}`}
|
|
||||||
value={_id}
|
|
||||||
className={`${currentWorkspace?._id === _id && "bg-mineshaft-600"}`}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||||
<hr className="mt-1 mb-1 h-px border-0 bg-gray-700" />
|
<button type="button" onClick={logOutUser} className="w-full">
|
||||||
<div className="w-full">
|
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||||
<Button
|
</button>
|
||||||
className="w-full bg-mineshaft-700 py-2 text-bunker-200"
|
</DropdownMenuContent>
|
||||||
colorSchema="primary"
|
</DropdownMenu>
|
||||||
variant="outline_bg"
|
<DropdownMenu>
|
||||||
size="sm"
|
<DropdownMenuTrigger
|
||||||
onClick={() => {
|
asChild
|
||||||
if (isAddingProjectsAllowed) {
|
className="p-1 hover:bg-primary-400 hover:text-black data-[state=open]:bg-primary-400 data-[state=open]:text-black"
|
||||||
handlePopUpOpen("addNewWs")
|
>
|
||||||
} else {
|
<div className="child flex h-6 w-6 items-center justify-center rounded-full bg-mineshaft pr-1 text-xs text-mineshaft-300 hover:bg-mineshaft-500">
|
||||||
handlePopUpOpen("upgradePlan");
|
{user?.firstName?.charAt(0)}
|
||||||
}
|
{user?.lastName && user?.lastName?.charAt(0)}
|
||||||
}}
|
</div>
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.email}</div>
|
||||||
|
<Link href="/personal-settings">
|
||||||
|
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/getting-started/introduction"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
|
||||||
>
|
>
|
||||||
Add Project
|
<DropdownMenuItem>
|
||||||
</Button>
|
Documentation
|
||||||
</div>
|
<FontAwesomeIcon
|
||||||
</Select>
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.06rem] pl-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://join.slack.com/t/infisical-users/shared_invite/zt-1ye0tm8ab-899qZ6ZbpfESuo6TEikyOQ"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
Join Slack Community
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.06rem] pl-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</a>
|
||||||
|
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||||
|
<button type="button" onClick={logOutUser} className="w-full">
|
||||||
|
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||||
|
</button>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
) : <Link href={`/org/${currentOrg?._id}/overview`}><div className="pr-2 my-6 flex justify-center items-center text-mineshaft-300 hover:text-mineshaft-100 cursor-default text-sm">
|
)}
|
||||||
<FontAwesomeIcon icon={faArrowLeft} className="pr-3"/>
|
{!router.asPath.includes("org") &&
|
||||||
Back to organization
|
(!router.asPath.includes("personal") && currentWorkspace ? (
|
||||||
</div></Link>)}
|
<div className="mt-5 mb-4 w-full p-3">
|
||||||
|
<p className="ml-1.5 mb-1 text-xs font-semibold uppercase text-gray-400">
|
||||||
|
Project
|
||||||
|
</p>
|
||||||
|
<Select
|
||||||
|
defaultValue={currentWorkspace?._id}
|
||||||
|
value={currentWorkspace?._id}
|
||||||
|
className="w-full truncate bg-mineshaft-600 py-2.5 font-medium"
|
||||||
|
onValueChange={(value) => {
|
||||||
|
router.push(`/project/${value}/secrets`);
|
||||||
|
}}
|
||||||
|
position="popper"
|
||||||
|
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 z-50 max-h-96 border-gray-700"
|
||||||
|
>
|
||||||
|
<div className="no-scrollbar::-webkit-scrollbar h-full no-scrollbar">
|
||||||
|
{workspaces
|
||||||
|
.filter((ws) => ws.organization === currentOrg?._id)
|
||||||
|
.map(({ _id, name }) => (
|
||||||
|
<SelectItem
|
||||||
|
key={`ws-layout-list-${_id}`}
|
||||||
|
value={_id}
|
||||||
|
className={`${currentWorkspace?._id === _id && "bg-mineshaft-600"}`}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<hr className="mt-1 mb-1 h-px border-0 bg-gray-700" />
|
||||||
|
<div className="w-full">
|
||||||
|
<Button
|
||||||
|
className="w-full bg-mineshaft-700 py-2 text-bunker-200"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
if (isAddingProjectsAllowed) {
|
||||||
|
handlePopUpOpen("addNewWs");
|
||||||
|
} else {
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
>
|
||||||
|
Add Project
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Link href={`/org/${currentOrg?._id}/overview`}>
|
||||||
|
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
|
||||||
|
<FontAwesomeIcon icon={faArrowLeft} className="pr-3" />
|
||||||
|
Back to organization
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
<div className={`px-1 ${!router.asPath.includes("personal") ? "block" : "hidden"}`}>
|
<div className={`px-1 ${!router.asPath.includes("personal") ? "block" : "hidden"}`}>
|
||||||
{((router.asPath.includes("project") || router.asPath.includes("integrations")) && currentWorkspace) ? <Menu>
|
{((router.asPath.includes("project") || router.asPath.includes("integrations")) && currentWorkspace) ? <Menu>
|
||||||
<Link href={`/project/${currentWorkspace?._id}/secrets`} passHref>
|
<Link href={`/project/${currentWorkspace?._id}/secrets`} passHref>
|
||||||
@@ -486,20 +546,25 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
<Link href={`/org/${currentOrg?._id}/settings`} passHref>
|
<Link href={`/org/${currentOrg?._id}/settings`} passHref>
|
||||||
<a>
|
<a>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
isSelected={
|
isSelected={router.asPath === `/org/${currentOrg?._id}/settings`}
|
||||||
router.asPath === `/org/${currentOrg?._id}/settings`
|
|
||||||
}
|
|
||||||
icon="system-outline-109-slider-toggle-settings"
|
icon="system-outline-109-slider-toggle-settings"
|
||||||
>
|
>
|
||||||
Organization Settings
|
Organization Settings
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</Menu>}
|
</Menu>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`relative mt-10 ${subscription && subscription.slug === "starter" && !subscription.has_used_trial ? "mb-2" : "mb-4"} w-full px-3 text-mineshaft-400 cursor-default text-sm flex flex-col items-center`}>
|
<div
|
||||||
{/* <div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[9.9rem] ${router.asPath.includes("org") ? "bottom-[8.4rem]" : "bottom-[5.4rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-30`}/>
|
className={`relative mt-10 ${
|
||||||
|
subscription && subscription.slug === "starter" && !subscription.has_used_trial
|
||||||
|
? "mb-2"
|
||||||
|
: "mb-4"
|
||||||
|
} flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400`}
|
||||||
|
>
|
||||||
|
{/* <div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[9.9rem] ${router.asPath.includes("org") ? "bottom-[8.4rem]" : "bottom-[5.4rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-30`}/>
|
||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[10.7rem] ${router.asPath.includes("org") ? "bottom-[8.15rem]" : "bottom-[5.15rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-50`}/>
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[10.7rem] ${router.asPath.includes("org") ? "bottom-[8.15rem]" : "bottom-[5.15rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-50`}/>
|
||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[11.5rem] ${router.asPath.includes("org") ? "bottom-[7.9rem]" : "bottom-[4.9rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-70`}/>
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[11.5rem] ${router.asPath.includes("org") ? "bottom-[7.9rem]" : "bottom-[4.9rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-70`}/>
|
||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[12.3rem] ${router.asPath.includes("org") ? "bottom-[7.65rem]" : "bottom-[4.65rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-90`}/>
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[12.3rem] ${router.asPath.includes("org") ? "bottom-[7.65rem]" : "bottom-[4.65rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-90`}/>
|
||||||
@@ -527,22 +592,24 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div> */}
|
</div> */}
|
||||||
{router.asPath.includes("org") && <div
|
{router.asPath.includes("org") && (
|
||||||
onKeyDown={() => null}
|
<div
|
||||||
role="button"
|
onKeyDown={() => null}
|
||||||
tabIndex={0}
|
role="button"
|
||||||
onClick={() => router.push(`/org/${router.query.id}/members?action=invite`)}
|
tabIndex={0}
|
||||||
className="w-full"
|
onClick={() => router.push(`/org/${router.query.id}/members?action=invite`)}
|
||||||
>
|
className="w-full"
|
||||||
<div className="hover:text-mineshaft-200 duration-200 mb-3 pl-5 w-full">
|
>
|
||||||
<FontAwesomeIcon icon={faPlus} className="mr-3"/>
|
<div className="mb-3 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
||||||
Invite people
|
<FontAwesomeIcon icon={faPlus} className="mr-3" />
|
||||||
|
Invite people
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
)}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<div className="hover:text-mineshaft-200 duration-200 mb-2 pl-5 w-full">
|
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
||||||
<FontAwesomeIcon icon={faQuestion} className="px-[0.1rem] mr-3"/>
|
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
|
||||||
Help & Support
|
Help & Support
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -564,28 +631,33 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
))}
|
))}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
{subscription && subscription.slug === "starter" && !subscription.has_used_trial && (
|
{subscription &&
|
||||||
<button
|
subscription.slug === "starter" &&
|
||||||
type="button"
|
!subscription.has_used_trial && (
|
||||||
onClick={async () => {
|
<button
|
||||||
if (!subscription || !currentOrg) return;
|
type="button"
|
||||||
|
onClick={async () => {
|
||||||
// direct user to start pro trial
|
if (!subscription || !currentOrg) return;
|
||||||
const url = await mutateAsync({
|
|
||||||
orgId: currentOrg._id,
|
// direct user to start pro trial
|
||||||
success_url: window.location.href
|
const url = await mutateAsync({
|
||||||
});
|
orgId: currentOrg._id,
|
||||||
|
success_url: window.location.href
|
||||||
window.location.href = url;
|
});
|
||||||
}}
|
|
||||||
className="w-full mt-1.5"
|
window.location.href = url;
|
||||||
>
|
}}
|
||||||
<div className="hover:text-primary-400 text-mineshaft-300 duration-200 flex justify-left items-center py-1 bg-mineshaft-600 rounded-md hover:bg-mineshaft-500 mb-1.5 mt-1.5 pl-4 w-full">
|
className="mt-1.5 w-full"
|
||||||
<FontAwesomeIcon icon={faInfinity} className="mr-3 ml-0.5 py-2 text-primary"/>
|
>
|
||||||
Start Free Pro Trial
|
<div className="justify-left mb-1.5 mt-1.5 flex w-full items-center rounded-md bg-mineshaft-600 py-1 pl-4 text-mineshaft-300 duration-200 hover:bg-mineshaft-500 hover:text-primary-400">
|
||||||
</div>
|
<FontAwesomeIcon
|
||||||
</button>
|
icon={faInfinity}
|
||||||
)}
|
className="mr-3 ml-0.5 py-2 text-primary"
|
||||||
|
/>
|
||||||
|
Start Free Pro Trial
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -1,18 +1,59 @@
|
|||||||
|
import { Fragment } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Tab } from "@headlessui/react";
|
||||||
|
|
||||||
import { ProjectTabGroup } from "./components";
|
import NavHeader from "@app/components/navigation/NavHeader";
|
||||||
|
|
||||||
|
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
||||||
|
import { ProjectServiceTokensTab } from "./components/ProjectServiceTokensTab";
|
||||||
|
import { WebhooksTab } from "./components/WebhooksTab";
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ name: "General", key: "tab-project-general" },
|
||||||
|
{ name: "Service Tokens", key: "tab-project-service-tokens" },
|
||||||
|
{ name: "Webhooks", key: "tab-project-webhooks" }
|
||||||
|
];
|
||||||
|
|
||||||
export const ProjectSettingsPage = () => {
|
export const ProjectSettingsPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center bg-bunker-800 text-white w-full">
|
<div className="flex h-full w-full justify-center bg-bunker-800 px-6 text-white">
|
||||||
<div className="max-w-7xl w-full px-6">
|
<div className="w-full max-w-screen-lg">
|
||||||
<div className="mt-6 mb-6">
|
<div className="relative right-5 ml-4">
|
||||||
<p className="text-3xl font-semibold text-gray-200">
|
<NavHeader pageName={t("settings.project.title")} isProjectRelated />
|
||||||
{t("settings.project.title")}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<ProjectTabGroup />
|
<div className="my-8">
|
||||||
|
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
|
||||||
|
</div>
|
||||||
|
<Tab.Group>
|
||||||
|
<Tab.List className="mb-6 w-full border-b-2 border-mineshaft-800">
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<Tab as={Fragment} key={tab.key}>
|
||||||
|
{({ selected }) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`w-30 p-4 font-semibold outline-none ${
|
||||||
|
selected ? "border-b-2 border-white text-white" : "text-mineshaft-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab.name}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Tab>
|
||||||
|
))}
|
||||||
|
</Tab.List>
|
||||||
|
<Tab.Panels>
|
||||||
|
<Tab.Panel>
|
||||||
|
<ProjectGeneralTab />
|
||||||
|
</Tab.Panel>
|
||||||
|
<Tab.Panel>
|
||||||
|
<ProjectServiceTokensTab />
|
||||||
|
</Tab.Panel>
|
||||||
|
<Tab.Panel>
|
||||||
|
<WebhooksTab />
|
||||||
|
</Tab.Panel>
|
||||||
|
</Tab.Panels>
|
||||||
|
</Tab.Group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
import { Fragment } from "react"
|
|
||||||
import { Tab } from "@headlessui/react"
|
|
||||||
|
|
||||||
import { ProjectGeneralTab } from "../ProjectGeneralTab";
|
|
||||||
import { ProjectServiceTokensTab } from "../ProjectServiceTokensTab";
|
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{ name: "General", key: "tab-project-general" },
|
|
||||||
{ name: "Service Tokens", key: "tab-project-service-tokens" }
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ProjectTabGroup = () => {
|
|
||||||
return (
|
|
||||||
<Tab.Group>
|
|
||||||
<Tab.List className="mb-6 border-b-2 border-mineshaft-800 w-full">
|
|
||||||
{tabs.map((tab) => (
|
|
||||||
<Tab as={Fragment} key={tab.key}>
|
|
||||||
{({ selected }) => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${selected ? "border-b border-white text-white" : "text-mineshaft-400"}`}
|
|
||||||
>
|
|
||||||
{tab.name}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Tab>
|
|
||||||
))}
|
|
||||||
</Tab.List>
|
|
||||||
<Tab.Panels>
|
|
||||||
<Tab.Panel>
|
|
||||||
<ProjectGeneralTab />
|
|
||||||
</Tab.Panel>
|
|
||||||
<Tab.Panel>
|
|
||||||
<ProjectServiceTokensTab />
|
|
||||||
</Tab.Panel>
|
|
||||||
</Tab.Panels>
|
|
||||||
</Tab.Group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { ProjectTabGroup } from "./ProjectTabGroup";
|
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalClose,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
|
||||||
|
const formSchema = yup.object({
|
||||||
|
environment: yup.string().required().trim().label("Environment"),
|
||||||
|
webhookUrl: yup.string().url().required().trim().label("Webhook URL"),
|
||||||
|
webhookSecretKey: yup.string().trim().label("Secret Key"),
|
||||||
|
secretPath: yup.string().required().trim().label("Secret Path")
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TFormSchema = yup.InferType<typeof formSchema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
|
onCreateWebhook: (data: TFormSchema) => void;
|
||||||
|
environments?: Array<{ slug: string; name: string }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AddWebhookForm = ({
|
||||||
|
isOpen,
|
||||||
|
onOpenChange,
|
||||||
|
onCreateWebhook,
|
||||||
|
environments = []
|
||||||
|
}: Props) => {
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
register,
|
||||||
|
reset,
|
||||||
|
formState: { errors, isSubmitting }
|
||||||
|
} = useForm<TFormSchema>({
|
||||||
|
resolver: yupResolver(formSchema)
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
<ModalContent title="Create a new webhook">
|
||||||
|
<form onSubmit={handleSubmit(onCreateWebhook)}>
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="environment"
|
||||||
|
defaultValue={environments?.[0]?.slug}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Environment"
|
||||||
|
isRequired
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{environments.map(({ name, slug }) => (
|
||||||
|
<SelectItem value={slug} key={slug}>
|
||||||
|
{name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
label="Secret Path"
|
||||||
|
isRequired
|
||||||
|
isError={Boolean(errors?.secretPath)}
|
||||||
|
errorText={errors?.secretPath?.message}
|
||||||
|
>
|
||||||
|
<Input placeholder="/, /**/*" {...register("secretPath")} />
|
||||||
|
</FormControl>
|
||||||
|
<FormControl
|
||||||
|
label="Secret Key"
|
||||||
|
isError={Boolean(errors?.webhookSecretKey)}
|
||||||
|
errorText={errors?.webhookSecretKey?.message}
|
||||||
|
helperText="To generate webhook signature for verification"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Provided during webhook setup"
|
||||||
|
{...register("webhookSecretKey")}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl
|
||||||
|
label="Webhook URL"
|
||||||
|
isRequired
|
||||||
|
isError={Boolean(errors?.webhookUrl)}
|
||||||
|
errorText={errors?.webhookUrl?.message}
|
||||||
|
>
|
||||||
|
<Input {...register("webhookUrl")} />
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
type="submit"
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button variant="plain" colorSchema="secondary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
+279
@@ -0,0 +1,279 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faInfoCircle, faPlug, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
|
EmptyState,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
THead,
|
||||||
|
Tooltip,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import {
|
||||||
|
useCreateWebhook,
|
||||||
|
useDeleteWebhook,
|
||||||
|
useGetWebhooks,
|
||||||
|
useTestWebhook,
|
||||||
|
useUpdateWebhook
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { AddWebhookForm, TFormSchema } from "./AddWebhookForm";
|
||||||
|
|
||||||
|
export const WebhooksTab = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const workspaceId = currentWorkspace?._id || "";
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||||
|
"addWebhook",
|
||||||
|
"deleteWebhook"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const { data: webhooks, isLoading: isWebhooksLoading } = useGetWebhooks(workspaceId);
|
||||||
|
|
||||||
|
// mutation
|
||||||
|
const { mutateAsync: createWebhook } = useCreateWebhook();
|
||||||
|
const {
|
||||||
|
mutateAsync: testWebhook,
|
||||||
|
variables: testWebhookVars,
|
||||||
|
isLoading: isTestWebhookSubmitting
|
||||||
|
} = useTestWebhook();
|
||||||
|
const {
|
||||||
|
mutateAsync: updateWebhook,
|
||||||
|
variables: updateWebhookVars,
|
||||||
|
isLoading: isUpdateWebhookSubmitting
|
||||||
|
} = useUpdateWebhook();
|
||||||
|
const { mutateAsync: deleteWebhook } = useDeleteWebhook();
|
||||||
|
|
||||||
|
const handleWebhookCreate = async (data: TFormSchema) => {
|
||||||
|
try {
|
||||||
|
await createWebhook({
|
||||||
|
...data,
|
||||||
|
workspaceId
|
||||||
|
});
|
||||||
|
handlePopUpClose("addWebhook");
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully created webhook"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to create webhook"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWebhookDisable = async (webhookId: string, isDisabled: boolean) => {
|
||||||
|
try {
|
||||||
|
await updateWebhook({
|
||||||
|
webhookId,
|
||||||
|
workspaceId,
|
||||||
|
isDisabled
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully updated webhook"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update webhook"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWebhookDelete = async () => {
|
||||||
|
try {
|
||||||
|
const webhookId = popUp?.deleteWebhook?.data as string;
|
||||||
|
await deleteWebhook({
|
||||||
|
webhookId,
|
||||||
|
workspaceId
|
||||||
|
});
|
||||||
|
handlePopUpClose("deleteWebhook");
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully deleted webhook"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to delete webhook"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWebhookTest = async (webhookId: string) => {
|
||||||
|
try {
|
||||||
|
await testWebhook({
|
||||||
|
webhookId,
|
||||||
|
workspaceId
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully triggered webhook"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to trigger webhook"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 max-w-screen-lg rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<p className="text-xl font-semibold text-mineshaft-100">{t("settings.webhooks.title")}</p>
|
||||||
|
<Button
|
||||||
|
onClick={() => handlePopUpOpen("addWebhook")}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="mb-8 text-gray-400">{t("settings.webhooks.description")}</p>
|
||||||
|
<div>
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Td>URL</Td>
|
||||||
|
<Td>Environment</Td>
|
||||||
|
<Td>Secret Path</Td>
|
||||||
|
<Td>Status</Td>
|
||||||
|
<Td className="text-right">Action</Td>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isWebhooksLoading && <TableSkeleton columns={5} key="webhooks-loading" />}
|
||||||
|
{!isWebhooksLoading && webhooks && webhooks?.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={5}>
|
||||||
|
<EmptyState title="No webhooks found" icon={faPlug} />
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
{!isWebhooksLoading &&
|
||||||
|
webhooks?.map(
|
||||||
|
({
|
||||||
|
_id: id,
|
||||||
|
url,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
lastStatus,
|
||||||
|
isDisabled,
|
||||||
|
updatedAt,
|
||||||
|
lastRunErrorMessage
|
||||||
|
}) => (
|
||||||
|
<Tr key={id}>
|
||||||
|
<Td className="max-w-xs overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
|
||||||
|
{url}
|
||||||
|
</Td>
|
||||||
|
<Td>{environment}</Td>
|
||||||
|
<Td>{secretPath}</Td>
|
||||||
|
<Td>
|
||||||
|
{!lastStatus ? (
|
||||||
|
"-"
|
||||||
|
) : (
|
||||||
|
<div className="inline-flex w-min items-center rounded bg-mineshaft-600 px-2 py-0.5 text-sm">
|
||||||
|
{lastStatus}{" "}
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
<div className="text-xs">
|
||||||
|
<div>
|
||||||
|
Updated At: {dayjs(updatedAt).format("YYYY-MM-DD, hh:mm A")}
|
||||||
|
</div>
|
||||||
|
{lastRunErrorMessage && (
|
||||||
|
<div className="mt-2 text-red">
|
||||||
|
Error: {lastRunErrorMessage}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
className={`ml-1 ${
|
||||||
|
lastStatus === "failed" ? "text-red" : "text-green"
|
||||||
|
}`}
|
||||||
|
icon={faInfoCircle}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="flex items-center justify-end space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="star"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => handleWebhookTest(id)}
|
||||||
|
isDisabled={
|
||||||
|
isTestWebhookSubmitting && testWebhookVars?.webhookId === id
|
||||||
|
}
|
||||||
|
isLoading={isTestWebhookSubmitting && testWebhookVars?.webhookId === id}
|
||||||
|
>
|
||||||
|
Test
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline_bg"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => handleWebhookDisable(id, !isDisabled)}
|
||||||
|
isDisabled={
|
||||||
|
isUpdateWebhookSubmitting && updateWebhookVars?.webhookId === id
|
||||||
|
}
|
||||||
|
isLoading={
|
||||||
|
isUpdateWebhookSubmitting && updateWebhookVars?.webhookId === id
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isDisabled ? "Enable" : "Disable"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline_bg"
|
||||||
|
className="border-red-800 bg-red-800 hover:border-red-700 hover:bg-red-700"
|
||||||
|
colorSchema="danger"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => handlePopUpOpen("deleteWebhook", id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
<AddWebhookForm
|
||||||
|
environments={currentWorkspace?.environments}
|
||||||
|
isOpen={popUp?.addWebhook?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("addWebhook", isOpen)}
|
||||||
|
onCreateWebhook={handleWebhookCreate}
|
||||||
|
/>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteWebhook.isOpen}
|
||||||
|
deleteKey="remove"
|
||||||
|
title="Are you sure you want to delete this webhook?"
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteWebhook", isOpen)}
|
||||||
|
onClose={() => handlePopUpClose("deleteWebhook")}
|
||||||
|
onDeleteApproved={handleWebhookDelete}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { WebhooksTab } from "./WebhooksTab";
|
||||||
@@ -4,6 +4,5 @@ export { E2EESection } from "./E2EESection";
|
|||||||
export { EnvironmentSection } from "./EnvironmentSection";
|
export { EnvironmentSection } from "./EnvironmentSection";
|
||||||
export { ProjectIndexSecretsSection } from "./ProjectIndexSecretsSection";
|
export { ProjectIndexSecretsSection } from "./ProjectIndexSecretsSection";
|
||||||
export { ProjectNameChangeSection } from "./ProjectNameChangeSection";
|
export { ProjectNameChangeSection } from "./ProjectNameChangeSection";
|
||||||
export { ProjectTabGroup } from "./ProjectTabGroup";
|
|
||||||
export { SecretTagsSection } from "./SecretTagsSection";
|
export { SecretTagsSection } from "./SecretTagsSection";
|
||||||
export { ServiceTokenSection } from "./ServiceTokenSection";
|
export { ServiceTokenSection } from "./ServiceTokenSection";
|
||||||
|
|||||||
Reference in New Issue
Block a user