From 00495568e4bd9b59b14a09d7e7f10fca6fd18a48 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Wed, 26 Oct 2022 16:20:26 -0300 Subject: [PATCH] refactor: Make the audit log looks like a timeline (#4765) --- .../components/AuditLogRow/AuditLogRow.tsx | 175 +++++++++++------- site/src/components/BuildsTable/BuildRow.tsx | 4 +- .../components/TableDateRow/TableDateRow.tsx | 43 +++++ site/src/pages/AuditPage/AuditPageView.tsx | 51 +++-- 4 files changed, 195 insertions(+), 78 deletions(-) create mode 100644 site/src/components/TableDateRow/TableDateRow.tsx diff --git a/site/src/components/AuditLogRow/AuditLogRow.tsx b/site/src/components/AuditLogRow/AuditLogRow.tsx index 478c01e05a..3f4bcea2e0 100644 --- a/site/src/components/AuditLogRow/AuditLogRow.tsx +++ b/site/src/components/AuditLogRow/AuditLogRow.tsx @@ -10,15 +10,19 @@ import { import { Pill } from "components/Pill/Pill" import { Stack } from "components/Stack/Stack" import { UserAvatar } from "components/UserAvatar/UserAvatar" -import { ComponentProps, useState } from "react" -import { MONOSPACE_FONT_FAMILY } from "theme/constants" +import { useState } from "react" +import { PaletteIndex } from "theme/palettes" import userAgentParser from "ua-parser-js" -import { createDayString } from "util/createDayString" +import { combineClasses } from "util/combineClasses" import { AuditLogDiff } from "./AuditLogDiff" -const pillTypeByHttpStatus = ( - httpStatus: number, -): ComponentProps["type"] => { +const readableActionMessage = (auditLog: AuditLog) => { + return auditLog.description + .replace("{user}", `${auditLog.user?.username.trim()}`) + .replace("{target}", `${auditLog.resource_target.trim()}`) +} + +const httpStatusColor = (httpStatus: number): PaletteIndex => { if (httpStatus >= 300 && httpStatus < 500) { return "warning" } @@ -30,12 +34,6 @@ const pillTypeByHttpStatus = ( return "success" } -const readableActionMessage = (auditLog: AuditLog) => { - return auditLog.description - .replace("{user}", `${auditLog.user?.username}`) - .replace("{target}", `${auditLog.resource_target}`) -} - export interface AuditLogRowProps { auditLog: AuditLog // Useful for Storybook @@ -63,13 +61,19 @@ export const AuditLogRow: React.FC = ({ } return ( - + { @@ -81,51 +85,61 @@ export const AuditLogRow: React.FC = ({ - + -
- - - {createDayString(auditLog.time)} - -
-
- - -
- IP {auditLog.ip ?? notAvailableLabel} -
-
- OS {os.name ?? notAvailableLabel} -
-
- Browser {displayBrowserInfo} -
+ + + + {new Date(auditLog.time).toLocaleTimeString()} + + + + + + + IP: {auditLog.ip ?? notAvailableLabel} + + + + OS: {os.name ?? notAvailableLabel} + + + + Browser: {displayBrowserInfo} + + + + +
@@ -150,27 +164,58 @@ export const AuditLogRow: React.FC = ({ const useStyles = makeStyles((theme) => ({ auditLogCell: { padding: "0 !important", + border: 0, }, auditLogRow: { + position: "relative", + + "&:focus": { + outlineStyle: "solid", + outlineOffset: -1, + outlineWidth: 2, + outlineColor: theme.palette.secondary.dark, + }, + + "&:not(:last-child) td:before": { + position: "absolute", + top: 20, + left: 50, + display: "block", + content: "''", + height: "100%", + width: 2, + background: theme.palette.divider, + }, + }, + + auditLogHeader: { padding: theme.spacing(2, 4), + }, + + clickable: { + cursor: "pointer", "&:hover": { backgroundColor: theme.palette.action.hover, }, }, - auditLogRowInfo: { + auditLogHeaderInfo: { flex: 1, }, - auditLogResume: { + auditLogSummary: { ...theme.typography.body1, fontFamily: "inherit", - display: "block", }, auditLogTime: { + color: theme.palette.text.secondary, + fontSize: 12, + }, + + auditLogInfo: { ...theme.typography.body2, fontSize: 12, fontFamily: "inherit", @@ -178,18 +223,20 @@ const useStyles = makeStyles((theme) => ({ display: "block", }, - auditLogRight: { - width: "auto", - }, - - auditLogExtraInfo: { - ...theme.typography.body2, - fontFamily: MONOSPACE_FONT_FAMILY, - color: theme.palette.text.secondary, - whiteSpace: "nowrap", - }, // offset the absence of the arrow icon on diff-less logs columnWithoutDiff: { marginLeft: "24px", }, + + fullWidth: { + width: "100%", + }, + + httpStatusPill: { + fontSize: 10, + height: 20, + paddingLeft: 10, + paddingRight: 10, + fontWeight: 600, + }, })) diff --git a/site/src/components/BuildsTable/BuildRow.tsx b/site/src/components/BuildsTable/BuildRow.tsx index da9c63ed5c..2fe032472c 100644 --- a/site/src/components/BuildsTable/BuildRow.tsx +++ b/site/src/components/BuildsTable/BuildRow.tsx @@ -43,7 +43,7 @@ export const BuildRow: React.FC = ({ build }) => {
({ borderBottom: 0, }, - buildResume: { + buildSummary: { ...theme.typography.body1, fontFamily: "inherit", }, diff --git a/site/src/components/TableDateRow/TableDateRow.tsx b/site/src/components/TableDateRow/TableDateRow.tsx new file mode 100644 index 0000000000..8895d6fb0b --- /dev/null +++ b/site/src/components/TableDateRow/TableDateRow.tsx @@ -0,0 +1,43 @@ +import { makeStyles } from "@material-ui/core/styles" +import TableCell from "@material-ui/core/TableCell" +import TableRow from "@material-ui/core/TableRow" +import formatRelative from "date-fns/formatRelative" +import { FC } from "react" + +export interface TableDateRow { + date: Date +} + +export const TableDateRow: FC = ({ date }) => { + const styles = useStyles() + // We only want the message related to the date since the time is displayed + // inside of the build row + const displayDate = formatRelative(date, new Date()).split("at")[0] + + return ( + + + {displayDate} + + + ) +} + +const useStyles = makeStyles((theme) => ({ + dateRow: { + background: theme.palette.background.paper, + + "&:not(:first-child) td": { + borderTop: `1px solid ${theme.palette.divider}`, + }, + }, + + dateCell: { + padding: `${theme.spacing(1, 4)} !important`, + background: `${theme.palette.background.paperLight} !important`, + fontSize: 12, + position: "relative", + color: theme.palette.text.secondary, + textTransform: "capitalize", + }, +})) diff --git a/site/src/pages/AuditPage/AuditPageView.tsx b/site/src/pages/AuditPage/AuditPageView.tsx index 5ec1dd35d1..824cd9e4df 100644 --- a/site/src/pages/AuditPage/AuditPageView.tsx +++ b/site/src/pages/AuditPage/AuditPageView.tsx @@ -2,7 +2,6 @@ import Table from "@material-ui/core/Table" import TableBody from "@material-ui/core/TableBody" import TableCell from "@material-ui/core/TableCell" import TableContainer from "@material-ui/core/TableContainer" -import TableHead from "@material-ui/core/TableHead" import TableRow from "@material-ui/core/TableRow" import { AuditLog } from "api/typesGenerated" import { AuditLogRow } from "components/AuditLogRow/AuditLogRow" @@ -16,9 +15,10 @@ import { import { PaginationWidget } from "components/PaginationWidget/PaginationWidget" import { SearchBarWithFilter } from "components/SearchBarWithFilter/SearchBarWithFilter" import { Stack } from "components/Stack/Stack" +import { TableDateRow } from "components/TableDateRow/TableDateRow" import { TableLoader } from "components/TableLoader/TableLoader" import { AuditHelpTooltip } from "components/Tooltips" -import { FC } from "react" +import { FC, Fragment } from "react" import { PaginationMachineRef } from "xServices/pagination/paginationXService" export const Language = { @@ -37,6 +37,27 @@ const presetFilters = [ { query: "resource_type:user action:delete", name: "Deleted users" }, ] +const groupAuditLogsByDate = (auditLogs?: AuditLog[]) => { + const auditLogsByDate: Record = {} + + if (!auditLogs) { + return + } + + auditLogs.forEach((auditLog) => { + const dateKey = new Date(auditLog.time).toDateString() + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (auditLogsByDate[dateKey]) { + auditLogsByDate[dateKey].push(auditLog) + } else { + auditLogsByDate[dateKey] = [auditLog] + } + }) + + return auditLogsByDate +} + export interface AuditPageViewProps { auditLogs?: AuditLog[] count?: number @@ -54,7 +75,7 @@ export const AuditPageView: FC = ({ }) => { const isLoading = auditLogs === undefined || count === undefined const isEmpty = !isLoading && auditLogs.length === 0 - const hasResults = !isLoading && auditLogs.length > 0 + const auditLogsByDate = groupAuditLogsByDate(auditLogs) return ( @@ -77,17 +98,23 @@ export const AuditPageView: FC = ({ - - - Logs - - {isLoading && } - {hasResults && - auditLogs.map((auditLog) => ( - - ))} + + {auditLogsByDate && + Object.keys(auditLogsByDate).map((dateStr) => { + const auditLogs = auditLogsByDate[dateStr] + + return ( + + + {auditLogs.map((log) => ( + + ))} + + ) + })} + {isEmpty && (