-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEAT] Automated audit logging (#667)
* WIP event logging - new table for events and new settings view for viewing * WIP add logging * UI for log rows * rename files to Logging to prevent getting gitignore * add metadata for all logging events and colored badges in logs page * remove unneeded comment * cleanup namespace for logging * clean up backend calls * update logging to show to => from settings changes * add logging for invitations, created, deleted, and accepted * add logging for user created, updated, suspended, or removed * add logging for workspace deleted * add logging for chat logs exported * add logging for API keys, LLM, embedder, vector db, embed chat, and reset button * modify event logs * update to event log types * simplify rendering of event badges --------- Co-authored-by: timothycarambat <[email protected]>
- Loading branch information
1 parent
5d64f26
commit d789920
Showing
22 changed files
with
778 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { CaretDown, CaretUp } from "@phosphor-icons/react"; | ||
import { useEffect, useState } from "react"; | ||
|
||
export default function LogRow({ log }) { | ||
const [expanded, setExpanded] = useState(false); | ||
const [metadata, setMetadata] = useState(null); | ||
const [hasMetadata, setHasMetadata] = useState(false); | ||
|
||
useEffect(() => { | ||
function parseAndSetMetadata() { | ||
try { | ||
let data = JSON.parse(log.metadata); | ||
setHasMetadata(Object.keys(data)?.length > 0); | ||
setMetadata(data); | ||
} catch {} | ||
} | ||
parseAndSetMetadata(); | ||
}, [log.metadata]); | ||
|
||
const handleRowClick = () => { | ||
if (log.metadata !== "{}") { | ||
setExpanded(!expanded); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
<tr | ||
onClick={handleRowClick} | ||
className={`bg-transparent text-white text-opacity-80 text-sm font-medium ${ | ||
hasMetadata ? "cursor-pointer hover:bg-white/5" : "" | ||
}`} | ||
> | ||
<EventBadge event={log.event} /> | ||
<td className="px-6 py-4 border-transparent transform transition-transform duration-200"> | ||
{log.user.username} | ||
</td> | ||
<td className="px-6 py-4 border-transparent transform transition-transform duration-200"> | ||
{log.occurredAt} | ||
</td> | ||
{hasMetadata && ( | ||
<> | ||
{expanded ? ( | ||
<td | ||
className={`px-2 gap-x-1 flex items-center justify-center transform transition-transform duration-200 hover:scale-105`} | ||
> | ||
<CaretUp weight="bold" size={20} /> | ||
<p className="text-xs text-white/50 w-[20px]">hide</p> | ||
</td> | ||
) : ( | ||
<td | ||
className={`px-2 gap-x-1 flex items-center justify-center transform transition-transform duration-200 hover:scale-105`} | ||
> | ||
<CaretDown weight="bold" size={20} /> | ||
<p className="text-xs text-white/50 w-[20px]">show</p> | ||
</td> | ||
)} | ||
</> | ||
)} | ||
</tr> | ||
<EventMetadata metadata={metadata} expanded={expanded} /> | ||
</> | ||
); | ||
} | ||
|
||
const EventMetadata = ({ metadata, expanded = false }) => { | ||
if (!metadata || !expanded) return null; | ||
return ( | ||
<tr className="bg-sidebar"> | ||
<td | ||
colSpan="2" | ||
className="px-6 py-4 font-medium text-white rounded-l-2xl" | ||
> | ||
Event Metadata | ||
</td> | ||
<td colSpan="4" className="px-6 py-4 rounded-r-2xl"> | ||
<div className="w-full rounded-lg bg-main-2 p-2 text-white shadow-sm border-white border bg-opacity-10"> | ||
<pre className="overflow-scroll"> | ||
{JSON.stringify(metadata, null, 2)} | ||
</pre> | ||
</div> | ||
</td> | ||
</tr> | ||
); | ||
}; | ||
|
||
const EventBadge = ({ event }) => { | ||
let colorTheme = { bg: "bg-sky-600/20", text: "text-sky-400 " }; | ||
if (event.includes("update")) | ||
colorTheme = { bg: "bg-yellow-600/20", text: "text-yellow-400 " }; | ||
if (event.includes("failed_") || event.includes("deleted")) | ||
colorTheme = { bg: "bg-red-600/20", text: "text-red-400 " }; | ||
if (event === "login_event") | ||
colorTheme = { bg: "bg-green-600/20", text: "text-green-400 " }; | ||
|
||
return ( | ||
<td className="px-6 py-4 font-medium whitespace-nowrap text-white flex items-center"> | ||
<span | ||
className={`rounded-full ${colorTheme.bg} px-2 py-0.5 text-sm font-medium ${colorTheme.text} shadow-sm`} | ||
> | ||
{event} | ||
</span> | ||
</td> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import Sidebar, { SidebarMobileHeader } from "@/components/SettingsSidebar"; | ||
import useQuery from "@/hooks/useQuery"; | ||
import System from "@/models/system"; | ||
import { useEffect, useState } from "react"; | ||
import { isMobile } from "react-device-detect"; | ||
import * as Skeleton from "react-loading-skeleton"; | ||
import LogRow from "./LogRow"; | ||
import showToast from "@/utils/toast"; | ||
|
||
export default function AdminLogs() { | ||
const handleResetLogs = async () => { | ||
if ( | ||
!window.confirm( | ||
"Are you sure you want to clear all event logs? This action is irreversible." | ||
) | ||
) | ||
return; | ||
const { success, error } = await System.clearEventLogs(); | ||
if (success) { | ||
showToast("Event logs cleared successfully.", "success"); | ||
setTimeout(() => { | ||
window.location.reload(); | ||
}, 1000); | ||
} else { | ||
showToast(`Failed to clear logs: ${error}`, "error"); | ||
} | ||
}; | ||
return ( | ||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex"> | ||
{!isMobile && <Sidebar />} | ||
<div | ||
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }} | ||
className="transition-all duration-500 relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[26px] bg-main-gradient w-full h-full overflow-y-scroll border-4 border-accent" | ||
> | ||
{isMobile && <SidebarMobileHeader />} | ||
<div className="flex flex-col w-full px-1 md:px-20 md:py-12 py-16"> | ||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10"> | ||
<div className="items-center flex gap-x-4"> | ||
<p className="text-2xl font-semibold text-white">Event Logs</p> | ||
<button | ||
onClick={handleResetLogs} | ||
className="px-4 py-1 rounded-lg text-slate-200/50 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800" | ||
> | ||
Clear event logs | ||
</button> | ||
</div> | ||
<p className="text-sm font-base text-white text-opacity-60"> | ||
View all actions and events happening on this instance for | ||
monitoring. | ||
</p> | ||
</div> | ||
<LogsContainer /> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
function LogsContainer() { | ||
const query = useQuery(); | ||
const [loading, setLoading] = useState(true); | ||
const [logs, setLogs] = useState([]); | ||
const [offset, setOffset] = useState(Number(query.get("offset") || 0)); | ||
const [canNext, setCanNext] = useState(false); | ||
|
||
const handlePrevious = () => { | ||
setOffset(Math.max(offset - 1, 0)); | ||
}; | ||
const handleNext = () => { | ||
setOffset(offset + 1); | ||
}; | ||
|
||
useEffect(() => { | ||
async function fetchLogs() { | ||
const { logs: _logs, hasPages = false } = await System.eventLogs(offset); | ||
setLogs(_logs); | ||
setCanNext(hasPages); | ||
setLoading(false); | ||
} | ||
fetchLogs(); | ||
}, [offset]); | ||
|
||
if (loading) { | ||
return ( | ||
<Skeleton.default | ||
height="80vh" | ||
width="100%" | ||
highlightColor="#3D4147" | ||
baseColor="#2C2F35" | ||
count={1} | ||
className="w-full p-4 rounded-b-2xl rounded-tr-2xl rounded-tl-sm mt-6" | ||
containerClassName="flex w-full" | ||
/> | ||
); | ||
} | ||
|
||
return ( | ||
<> | ||
<table className="md:w-5/6 w-full text-sm text-left rounded-lg mt-5"> | ||
<thead className="text-white text-opacity-80 text-sm font-bold uppercase border-white border-b border-opacity-60"> | ||
<tr> | ||
<th scope="col" className="px-6 py-3"> | ||
Event Type | ||
</th> | ||
<th scope="col" className="px-6 py-3"> | ||
User | ||
</th> | ||
<th scope="col" className="px-6 py-3"> | ||
Occurred At | ||
</th> | ||
<th scope="col" className="px-6 py-3 rounded-tr-lg"> | ||
{" "} | ||
</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{!!logs && logs.map((log) => <LogRow key={log.id} log={log} />)} | ||
</tbody> | ||
</table> | ||
<div className="flex w-full justify-between items-center"> | ||
<button | ||
onClick={handlePrevious} | ||
className="px-4 py-2 rounded-lg border border-slate-200 text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 disabled:invisible" | ||
disabled={offset === 0} | ||
> | ||
Previous Page | ||
</button> | ||
<button | ||
onClick={handleNext} | ||
className="px-4 py-2 rounded-lg border border-slate-200 text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 disabled:invisible" | ||
disabled={!canNext} | ||
> | ||
Next Page | ||
</button> | ||
</div> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.