From 0425a085feea885bd1b2b2572dd21f6b57c98d2d Mon Sep 17 00:00:00 2001 From: x032205 Date: Sat, 1 Nov 2025 03:43:30 -0400 Subject: [PATCH] Format row outputs into tables, and only allow one log expanded at a time --- .../components/PamSessionLogOutput.tsx | 95 +++++++++++++++++++ .../components/PamSessionLogsSection.tsx | 23 +++-- 2 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogOutput.tsx diff --git a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogOutput.tsx b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogOutput.tsx new file mode 100644 index 000000000..92d173f7e --- /dev/null +++ b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogOutput.tsx @@ -0,0 +1,95 @@ +import { useState } from "react"; + +type TableLog = { + command?: string; + data_rows: Record[]; + total_rows?: number; +}; + +export const PamSessionLogOutput = ({ content }: { content: string }) => { + const [isRawView, setIsRawView] = useState(false); + + let parsedContent: TableLog | null = null; + try { + const parsed = JSON.parse(content); + if ( + parsed && + typeof parsed === "object" && + !Array.isArray(parsed) && + parsed.data_rows && + Array.isArray(parsed.data_rows) && + parsed.data_rows.length > 0 && + typeof parsed.data_rows[0] === "object" && + parsed.data_rows[0] !== null + ) { + parsedContent = parsed; + } + } catch (error) { + // Not a valid JSON or doesn't match structure, will render as plain text + } + + if (parsedContent) { + const headers = Object.keys(parsedContent.data_rows[0]); + return ( +
+ {isRawView ? ( +
{content}
+ ) : ( + <> + {parsedContent.command && ( +
{`> ${parsedContent.command}`}
+ )} +
+ + + + {headers.map((header) => ( + + ))} + + + + {parsedContent.data_rows.map((row, rowIndex) => ( + + {headers.map((header) => ( + + ))} + + ))} + +
+ {header.replace(/_/g, " ")} +
+ {String(row[header] ?? "")} +
+
+ + )} +
+ + + {parsedContent.total_rows !== undefined && ( +
+ Total rows: {parsedContent.total_rows} +
+ )} +
+
+ ); + } + + return
{content}
; +}; diff --git a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx index 66d2fad8d..d620cf4cc 100644 --- a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx +++ b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx @@ -3,6 +3,7 @@ import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { TPamSession } from "@app/hooks/api/pam"; +import { PamSessionLogOutput } from "./PamSessionLogOutput"; const formatLogContent = (text: string | null | undefined): string => { if (!text) return ""; @@ -59,13 +60,10 @@ export const PamSessionLogsSection = ({ session }: Props) => { const toggleExpand = (timestamp: string) => { setExpandedLogTimestamps((prev) => { - const newSet = new Set(prev); - if (newSet.has(timestamp)) { - newSet.delete(timestamp); - } else { - newSet.add(timestamp); + if (prev.has(timestamp)) { + return new Set(); } - return newSet; + return new Set([timestamp]); }); }; @@ -109,9 +107,16 @@ export const PamSessionLogsSection = ({ session }: Props) => { {isExpanded && log.output && ( -
- {formattedOutput} -
+ <> +
+
+ OUTPUT +
+
+
+ +
+ )} );