mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(ui): audit log page completed
This commit is contained in:
@@ -4,10 +4,12 @@ import { Tab } from "@headlessui/react";
|
|||||||
|
|
||||||
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
||||||
import { WebhooksTab } from "./components/WebhooksTab";
|
import { WebhooksTab } from "./components/WebhooksTab";
|
||||||
|
import { AuditLogStreamsTab } from "./components";
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: "General", key: "tab-project-general" },
|
{ name: "General", key: "tab-project-general" },
|
||||||
{ name: "Webhooks", key: "tab-project-webhooks" }
|
{ name: "Webhooks", key: "tab-project-webhooks" },
|
||||||
|
{ name: "Audit Log Streams", key: "tab-project-audit-log-stream" }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ProjectSettingsPage = () => {
|
export const ProjectSettingsPage = () => {
|
||||||
@@ -25,9 +27,8 @@ export const ProjectSettingsPage = () => {
|
|||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`w-30 mx-2 mr-4 py-2 text-sm font-medium outline-none ${
|
className={`w-30 mx-2 mr-4 py-2 text-sm font-medium outline-none ${selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
||||||
selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{tab.name}
|
{tab.name}
|
||||||
</button>
|
</button>
|
||||||
@@ -42,6 +43,9 @@ export const ProjectSettingsPage = () => {
|
|||||||
<Tab.Panel>
|
<Tab.Panel>
|
||||||
<WebhooksTab />
|
<WebhooksTab />
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
|
<Tab.Panel>
|
||||||
|
<AuditLogStreamsTab />
|
||||||
|
</Tab.Panel>
|
||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, FormControl, Input, Spinner } from "@app/components/v2";
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
|
import {
|
||||||
|
useCreateAuditLogStream,
|
||||||
|
useGetAuditLogStreamDetails,
|
||||||
|
useUpdateAuditLogStream
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id?: string;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
url: z.string().url().min(1),
|
||||||
|
token: z.string().optional()
|
||||||
|
});
|
||||||
|
type TForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const AuditLogStreamForm = ({ id = "", onClose }: Props) => {
|
||||||
|
const isEdit = Boolean(id);
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const projectSlug = currentWorkspace?.slug || "";
|
||||||
|
|
||||||
|
const auditLogStream = useGetAuditLogStreamDetails(id);
|
||||||
|
const createAuditLogStream = useCreateAuditLogStream();
|
||||||
|
const updateAuditLogStream = useUpdateAuditLogStream();
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<TForm>({
|
||||||
|
values: auditLogStream?.data
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAuditLogStreamEdit = async ({ token, url }: TForm) => {
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
await updateAuditLogStream.mutateAsync({
|
||||||
|
id,
|
||||||
|
projectSlug,
|
||||||
|
token,
|
||||||
|
url
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully updated stream"
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update stream"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormSubmit = async ({ token, url }: TForm) => {
|
||||||
|
if (isSubmitting) return;
|
||||||
|
if (isEdit) {
|
||||||
|
handleAuditLogStreamEdit({ token, url });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await createAuditLogStream.mutateAsync({
|
||||||
|
projectSlug,
|
||||||
|
token,
|
||||||
|
url
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully created stream"
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to create stream"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isEdit && auditLogStream.isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center p-8">
|
||||||
|
<Spinner size="lg" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="url"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl label="URL" isError={Boolean(error?.message)} errorText={error?.message}>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="token"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Token"
|
||||||
|
isOptional
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
helperText="A token serves for identification and is sent in Authorization Bearer format."
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 flex items-center">
|
||||||
|
<Button className="mr-4" type="submit" isLoading={isSubmitting}>
|
||||||
|
{isEdit ? "Save" : "Create"}
|
||||||
|
</Button>
|
||||||
|
<Button variant="plain" colorSchema="secondary" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import { faPlug, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
|
EmptyState,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
THead,
|
||||||
|
Tr,
|
||||||
|
UpgradePlanModal
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useSubscription,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
|
import { withProjectPermission } from "@app/hoc";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { useDeleteAuditLogStream, useGetAuditLogStreams } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { AuditLogStreamForm } from "./AuditLogStreamForm";
|
||||||
|
|
||||||
|
export const AuditLogStreamsTab = withProjectPermission(
|
||||||
|
() => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const projectSlug = currentWorkspace?.slug || "";
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||||
|
"auditLogStreamForm",
|
||||||
|
"deleteAuditLogStream",
|
||||||
|
"upgradePlan"
|
||||||
|
] as const);
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
|
const { data: auditLogStreams, isLoading: isAuditLogStreamsLoading } =
|
||||||
|
useGetAuditLogStreams(projectSlug);
|
||||||
|
|
||||||
|
// mutation
|
||||||
|
const { mutateAsync: deleteAuditLogStream } = useDeleteAuditLogStream();
|
||||||
|
|
||||||
|
const handleAuditLogStreamDelete = async () => {
|
||||||
|
try {
|
||||||
|
const auditLogStreamId = popUp?.deleteAuditLogStream?.data as string;
|
||||||
|
await deleteAuditLogStream({
|
||||||
|
id: auditLogStreamId,
|
||||||
|
projectSlug
|
||||||
|
});
|
||||||
|
handlePopUpClose("deleteAuditLogStream");
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully deleted stream"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to delete stream"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 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">Audit Log Streams</p>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Create}
|
||||||
|
a={ProjectPermissionSub.Settings}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (subscription && !subscription?.auditLogStreams) {
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handlePopUpOpen("auditLogStreamForm");
|
||||||
|
}}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
<p className="mb-8 text-gray-400">
|
||||||
|
Manage audit log streams to send audit log to any logging providers with syslog support.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Td>URL</Td>
|
||||||
|
<Td className="text-right">Action</Td>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isAuditLogStreamsLoading && (
|
||||||
|
<TableSkeleton columns={2} innerKey="stream-loading" />
|
||||||
|
)}
|
||||||
|
{!isAuditLogStreamsLoading && auditLogStreams && auditLogStreams?.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={5}>
|
||||||
|
<EmptyState title="No audit log streams found" icon={faPlug} />
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
{!isAuditLogStreamsLoading &&
|
||||||
|
auditLogStreams?.map(({ id, url }) => (
|
||||||
|
<Tr key={id}>
|
||||||
|
<Td className="max-w-xs overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
|
||||||
|
{url}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="flex items-center justify-end space-x-2">
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.Settings}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
variant="outline_bg"
|
||||||
|
size="xs"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
onClick={() => handlePopUpOpen("auditLogStreamForm", id)}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Delete}
|
||||||
|
a={ProjectPermissionSub.Settings}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
variant="outline_bg"
|
||||||
|
className="border-red-800 bg-red-800 hover:border-red-700 hover:bg-red-700"
|
||||||
|
colorSchema="danger"
|
||||||
|
size="xs"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
onClick={() => handlePopUpOpen("deleteAuditLogStream", id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp.auditLogStreamForm.isOpen}
|
||||||
|
onOpenChange={(isModalOpen) => {
|
||||||
|
handlePopUpToggle("auditLogStreamForm", isModalOpen);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
title={`${popUp?.auditLogStreamForm?.data ? "Update" : "Create"} Audit Log Stream `}
|
||||||
|
subTitle="Streams enable the transmission of audit logs to third-party providers."
|
||||||
|
>
|
||||||
|
<AuditLogStreamForm
|
||||||
|
id={popUp?.auditLogStreamForm?.data as string}
|
||||||
|
onClose={() => handlePopUpToggle("auditLogStreamForm")}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
text="You can add audit log streams if you switch to Infisical's Enterprise plan."
|
||||||
|
/>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteAuditLogStream.isOpen}
|
||||||
|
deleteKey="delete"
|
||||||
|
title="Are you sure you want to remove this stream?"
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteAuditLogStream", isOpen)}
|
||||||
|
onClose={() => handlePopUpClose("deleteAuditLogStream")}
|
||||||
|
onDeleteApproved={handleAuditLogStreamDelete}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Settings }
|
||||||
|
);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { AuditLogStreamsTab } from "./AuditLogStreamTab";
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export { AuditLogStreamsTab } from "./AuditLogStreamTab";
|
||||||
export { AutoCapitalizationSection } from "./AutoCapitalizationSection";
|
export { AutoCapitalizationSection } from "./AutoCapitalizationSection";
|
||||||
export { DeleteProjectSection } from "./DeleteProjectSection";
|
export { DeleteProjectSection } from "./DeleteProjectSection";
|
||||||
export { E2EESection } from "./E2EESection";
|
export { E2EESection } from "./E2EESection";
|
||||||
|
|||||||
Reference in New Issue
Block a user