Merge branch 'main' into git-scanning-app

This commit is contained in:
Maidul Islam
2023-07-17 17:20:04 -04:00
committed by GitHub
61 changed files with 1594 additions and 506 deletions
@@ -69,7 +69,7 @@ export const PreviewSection = () => {
return (
<div>
{subscription && subscription?.slug !== "enterprise" && subscription?.slug !== "pro" && subscription?.slug !== "pro-annual" && (
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600 mb-6 flex items-center bg-mineshaft-600 max-w-screen-lg">
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600 mb-6 flex items-center bg-mineshaft-600">
<div className="flex-1">
<h2 className="text-xl font-semibold text-mineshaft-50">Become Infisical</h2>
<p className="text-gray-400 mt-4">Unlimited members, projects, RBAC, smart alerts, and so much more</p>
@@ -84,7 +84,7 @@ export const PreviewSection = () => {
</div>
)}
{!isLoading && subscription && data && (
<div className="flex mb-6 max-w-screen-lg">
<div className="flex mb-6">
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 mr-4 border border-mineshaft-600">
<p className="mb-2 text-gray-400">Current plan</p>
<p className="text-2xl text-mineshaft-50 font-semibold mb-8">
@@ -1,18 +1,59 @@
import { Fragment } from "react";
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 = () => {
const { t } = useTranslation();
return (
<div className="flex justify-center bg-bunker-800 text-white w-full">
<div className="max-w-7xl w-full px-6">
<div className="mt-6 mb-6">
<p className="text-3xl font-semibold text-gray-200">
{t("settings.project.title")}
</p>
<div className="flex h-full w-full justify-center bg-bunker-800 px-6 text-white">
<div className="w-full max-w-screen-lg">
<div className="relative right-5 ml-4">
<NavHeader pageName={t("settings.project.title")} isProjectRelated />
</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>
);
@@ -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";
@@ -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>
);
};
@@ -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 { ProjectIndexSecretsSection } from "./ProjectIndexSecretsSection";
export { ProjectNameChangeSection } from "./ProjectNameChangeSection";
export { ProjectTabGroup } from "./ProjectTabGroup";
export { SecretTagsSection } from "./SecretTagsSection";
export { ServiceTokenSection } from "./ServiceTokenSection";