mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: refactor <AuditLogRow* /> (#22014)
This pull-request takes the MUI based components from `<AuditLogRow />` and its subsidiaries and updates them to use the correct newer Tailwind based components.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import Link from "@mui/material/Link";
|
||||
import type { AuditLog } from "api/typesGenerated";
|
||||
import { Link } from "components/Link/Link";
|
||||
import type { FC } from "react";
|
||||
import { Link as RouterLink } from "react-router";
|
||||
import { BuildAuditDescription } from "./BuildAuditDescription";
|
||||
@@ -52,8 +52,10 @@ export const AuditLogDescription: FC<AuditLogDescriptionProps> = ({
|
||||
<span>
|
||||
{truncatedDescription}
|
||||
{auditLog.resource_link ? (
|
||||
<Link component={RouterLink} to={auditLog.resource_link}>
|
||||
<strong>{target}</strong>
|
||||
<Link asChild showExternalIcon={false} className="text-base px-0">
|
||||
<RouterLink to={auditLog.resource_link}>
|
||||
<strong>{target}</strong>
|
||||
</RouterLink>
|
||||
</Link>
|
||||
) : (
|
||||
<strong>{target}</strong>
|
||||
@@ -70,8 +72,10 @@ function AppSessionAuditLogDescription({ auditLog }: AuditLogDescriptionProps) {
|
||||
return (
|
||||
<>
|
||||
{connection_type} session to {workspace_owner}'s{" "}
|
||||
<Link component={RouterLink} to={`${auditLog.resource_link}`}>
|
||||
<strong>{workspace_name}</strong>
|
||||
<Link asChild showExternalIcon={false} className="text-base px-0">
|
||||
<RouterLink to={`${auditLog.resource_link}`}>
|
||||
<strong>{workspace_name}</strong>
|
||||
</RouterLink>
|
||||
</Link>{" "}
|
||||
workspace{" "}
|
||||
<strong>{auditLog.action === "disconnect" ? "closed" : "opened"}</strong>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Link from "@mui/material/Link";
|
||||
import type { AuditLog } from "api/typesGenerated";
|
||||
import { Link } from "components/Link/Link";
|
||||
import { type FC, useMemo } from "react";
|
||||
import { Link as RouterLink } from "react-router";
|
||||
import { systemBuildReasons } from "utils/workspace";
|
||||
@@ -38,8 +38,10 @@ export const BuildAuditDescription: FC<BuildAuditDescriptionProps> = ({
|
||||
<span>
|
||||
{user} <strong>{action}</strong> workspace{" "}
|
||||
{auditLog.resource_link ? (
|
||||
<Link component={RouterLink} to={auditLog.resource_link}>
|
||||
<strong>{workspaceName}</strong>
|
||||
<Link asChild showExternalIcon={false} className="text-base px-0">
|
||||
<RouterLink to={auditLog.resource_link}>
|
||||
<strong>{workspaceName}</strong>
|
||||
</RouterLink>
|
||||
</Link>
|
||||
) : (
|
||||
<strong>{workspaceName}</strong>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { CSSObject, Interpolation, Theme } from "@emotion/react";
|
||||
import Collapse from "@mui/material/Collapse";
|
||||
import Link from "@mui/material/Link";
|
||||
import type { AuditLog, BuildReason } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
} from "components/Collapsible/Collapsible";
|
||||
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { Link } from "components/Link/Link";
|
||||
import { StatusPill } from "components/StatusPill/StatusPill";
|
||||
import { TableCell } from "components/Table/Table";
|
||||
import { TimelineEntry } from "components/Timeline/TimelineEntry";
|
||||
@@ -71,246 +72,186 @@ export const AuditLogRow: FC<AuditLogRowProps> = ({
|
||||
data-testid={`audit-log-row-${auditLog.id}`}
|
||||
clickable={shouldDisplayDiff}
|
||||
>
|
||||
<TableCell css={styles.auditLogCell}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
css={styles.auditLogHeader}
|
||||
tabIndex={0}
|
||||
onClick={toggle}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
toggle();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
css={styles.auditLogHeaderInfo}
|
||||
<TableCell className="!p-0 border-0 border-b text-base">
|
||||
<Collapsible open={isDiffOpen} onOpenChange={setIsDiffOpen}>
|
||||
<div
|
||||
className="flex flex-row items-center gap-4 py-4 px-8"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={toggle}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
toggle();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" alignItems="center" css={styles.fullWidth}>
|
||||
{/*
|
||||
* Session logs don't have an associated user to the log,
|
||||
* so when it happens we display a default icon to represent non user actions
|
||||
*/}
|
||||
{auditLog.user ? (
|
||||
<Avatar
|
||||
fallback={auditLog.user.username}
|
||||
src={auditLog.user.avatar_url}
|
||||
/>
|
||||
) : (
|
||||
<Avatar>
|
||||
<NetworkIcon className="h-full w-full p-1" />
|
||||
</Avatar>
|
||||
)}
|
||||
<div className="flex flex-row items-center gap-4 flex-1">
|
||||
<div className="flex flex-row items-center gap-4 w-full">
|
||||
{/*
|
||||
* Session logs don't have an associated user to the log,
|
||||
* so when it happens we display a default icon to represent non user actions
|
||||
*/}
|
||||
{auditLog.user ? (
|
||||
<Avatar
|
||||
fallback={auditLog.user.username}
|
||||
src={auditLog.user.avatar_url}
|
||||
/>
|
||||
) : (
|
||||
<Avatar>
|
||||
<NetworkIcon className="h-full w-full p-1" />
|
||||
</Avatar>
|
||||
)}
|
||||
|
||||
<Stack
|
||||
alignItems="baseline"
|
||||
css={styles.fullWidth}
|
||||
justifyContent="space-between"
|
||||
direction="row"
|
||||
>
|
||||
<Stack
|
||||
css={styles.auditLogSummary}
|
||||
direction="row"
|
||||
alignItems="baseline"
|
||||
spacing={1}
|
||||
>
|
||||
<AuditLogDescription auditLog={auditLog} />
|
||||
{auditLog.is_deleted && (
|
||||
<span css={styles.deletedLabel}>(deleted)</span>
|
||||
)}
|
||||
<span css={styles.auditLogTime}>
|
||||
{new Date(auditLog.time).toLocaleTimeString()}
|
||||
</span>
|
||||
</Stack>
|
||||
<div className="flex flex-row items-baseline justify-between w-full font-normal gap-4">
|
||||
<div className="flex flex-row items-baseline gap-2">
|
||||
<AuditLogDescription auditLog={auditLog} />
|
||||
{auditLog.is_deleted && (
|
||||
<span className="text-xs text-content-secondary">
|
||||
(deleted)
|
||||
</span>
|
||||
)}
|
||||
<span className="text-content-secondary text-xs">
|
||||
{new Date(auditLog.time).toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Stack direction="row" alignItems="center">
|
||||
<StatusPill isHttpCode={true} code={auditLog.status_code} />
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
<StatusPill isHttpCode={true} code={auditLog.status_code} />
|
||||
|
||||
{/* With multi-org, there is not enough space so show
|
||||
{/* With multi-org, there is not enough space so show
|
||||
everything in a tooltip. */}
|
||||
{showOrgDetails ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon
|
||||
css={(theme) => ({
|
||||
color: theme.palette.info.light,
|
||||
})}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<div css={styles.auditLogInfoTooltip}>
|
||||
{auditLog.ip && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>IP:</h4>
|
||||
<div>{auditLog.ip}</div>
|
||||
</div>
|
||||
)}
|
||||
{userAgent?.os.name && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>OS:</h4>
|
||||
<div>{userAgent.os.name}</div>
|
||||
</div>
|
||||
)}
|
||||
{userAgent?.browser.name && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>Browser:</h4>
|
||||
{showOrgDetails ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon className="text-content-link" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<div className="flex flex-col gap-2">
|
||||
{auditLog.ip && (
|
||||
<div>
|
||||
{userAgent.browser.name}{" "}
|
||||
{userAgent.browser.version}
|
||||
<h4 className="m-0 text-content-primary leading-[150%] font-semibold">
|
||||
IP:
|
||||
</h4>
|
||||
<div>{auditLog.ip}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{auditLog.organization && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>
|
||||
Organization:
|
||||
</h4>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/organizations/${auditLog.organization.name}`}
|
||||
>
|
||||
{auditLog.organization.display_name ||
|
||||
auditLog.organization.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{auditLog.additional_fields?.build_reason &&
|
||||
auditLog.action === "start" && (
|
||||
)}
|
||||
{userAgent?.os.name && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>Reason:</h4>
|
||||
<h4 className="m-0 text-content-primary leading-[150%] font-semibold">
|
||||
OS:
|
||||
</h4>
|
||||
<div>{userAgent.os.name}</div>
|
||||
</div>
|
||||
)}
|
||||
{userAgent?.browser.name && (
|
||||
<div>
|
||||
<h4 className="m-0 text-content-primary leading-[150%] font-semibold">
|
||||
Browser:
|
||||
</h4>
|
||||
<div>
|
||||
{
|
||||
buildReasonLabels[
|
||||
auditLog.additional_fields
|
||||
.build_reason as BuildReason
|
||||
]
|
||||
}
|
||||
{userAgent.browser.name}{" "}
|
||||
{userAgent.browser.version}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Stack direction="row" spacing={1} alignItems="baseline">
|
||||
{auditLog.ip && (
|
||||
<span css={styles.auditLogInfo}>
|
||||
<span>IP: </span>
|
||||
<strong>{auditLog.ip}</strong>
|
||||
</span>
|
||||
)}
|
||||
{userAgent?.os.name && (
|
||||
<span css={styles.auditLogInfo}>
|
||||
<span>OS: </span>
|
||||
<strong>{userAgent.os.name}</strong>
|
||||
</span>
|
||||
)}
|
||||
{userAgent?.browser.name && (
|
||||
<span css={styles.auditLogInfo}>
|
||||
<span>Browser: </span>
|
||||
<strong>
|
||||
{userAgent.browser.name} {userAgent.browser.version}
|
||||
</strong>
|
||||
</span>
|
||||
)}
|
||||
{auditLog.additional_fields?.build_reason &&
|
||||
auditLog.action === "start" && (
|
||||
<span css={styles.auditLogInfo}>
|
||||
<span>Reason: </span>
|
||||
{auditLog.organization && (
|
||||
<div>
|
||||
<h4 className="m-0 text-content-primary leading-[150%] font-semibold">
|
||||
Organization:
|
||||
</h4>
|
||||
<Link
|
||||
asChild
|
||||
showExternalIcon={false}
|
||||
className="px-0"
|
||||
>
|
||||
<RouterLink
|
||||
to={`/organizations/${auditLog.organization.name}`}
|
||||
>
|
||||
{auditLog.organization.display_name ||
|
||||
auditLog.organization.name}
|
||||
</RouterLink>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{auditLog.additional_fields?.build_reason &&
|
||||
auditLog.action === "start" && (
|
||||
<div>
|
||||
<h4 className="m-0 text-content-primary leading-normal font-semibold">
|
||||
Reason:
|
||||
</h4>
|
||||
<div>
|
||||
{
|
||||
buildReasonLabels[
|
||||
auditLog.additional_fields
|
||||
.build_reason as BuildReason
|
||||
]
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div className="flex flex-row items-baseline gap-2">
|
||||
{auditLog.ip && (
|
||||
<span className="text-xs text-content-secondary block">
|
||||
<span>IP: </span>
|
||||
<strong>{auditLog.ip}</strong>
|
||||
</span>
|
||||
)}
|
||||
{userAgent?.os.name && (
|
||||
<span className="text-xs text-content-secondary block">
|
||||
<span>OS: </span>
|
||||
<strong>{userAgent.os.name}</strong>
|
||||
</span>
|
||||
)}
|
||||
{userAgent?.browser.name && (
|
||||
<span className="text-xs text-content-secondary block">
|
||||
<span>Browser: </span>
|
||||
<strong>
|
||||
{
|
||||
buildReasonLabels[
|
||||
auditLog.additional_fields
|
||||
.build_reason as BuildReason
|
||||
]
|
||||
}
|
||||
{userAgent.browser.name}{" "}
|
||||
{userAgent.browser.version}
|
||||
</strong>
|
||||
</span>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
{auditLog.additional_fields?.build_reason &&
|
||||
auditLog.action === "start" && (
|
||||
<span className="text-xs text-content-secondary block">
|
||||
<span>Reason: </span>
|
||||
<strong>
|
||||
{
|
||||
buildReasonLabels[
|
||||
auditLog.additional_fields
|
||||
.build_reason as BuildReason
|
||||
]
|
||||
}
|
||||
</strong>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{shouldDisplayDiff ? (
|
||||
<div> {<DropdownArrow close={isDiffOpen} />}</div>
|
||||
) : (
|
||||
<div css={styles.columnWithoutDiff} />
|
||||
{shouldDisplayDiff ? (
|
||||
<div>
|
||||
<DropdownArrow close={isDiffOpen} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="ml-6" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{shouldDisplayDiff && (
|
||||
<CollapsibleContent>
|
||||
<AuditLogDiff diff={auditDiff} />
|
||||
</CollapsibleContent>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{shouldDisplayDiff && (
|
||||
<Collapse in={isDiffOpen}>
|
||||
<AuditLogDiff diff={auditDiff} />
|
||||
</Collapse>
|
||||
)}
|
||||
</Collapsible>
|
||||
</TableCell>
|
||||
</TimelineEntry>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
auditLogCell: {
|
||||
padding: "0 !important",
|
||||
border: 0,
|
||||
},
|
||||
|
||||
auditLogHeader: {
|
||||
padding: "16px 32px",
|
||||
},
|
||||
|
||||
auditLogHeaderInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
auditLogSummary: (theme) => ({
|
||||
...(theme.typography.body1 as CSSObject),
|
||||
fontFamily: "inherit",
|
||||
}),
|
||||
|
||||
auditLogTime: (theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: 12,
|
||||
}),
|
||||
|
||||
auditLogInfo: (theme) => ({
|
||||
...(theme.typography.body2 as CSSObject),
|
||||
fontSize: 12,
|
||||
fontFamily: "inherit",
|
||||
color: theme.palette.text.secondary,
|
||||
display: "block",
|
||||
}),
|
||||
|
||||
auditLogInfoHeader: (theme) => ({
|
||||
margin: 0,
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: 14,
|
||||
lineHeight: "150%",
|
||||
fontWeight: 600,
|
||||
}),
|
||||
|
||||
auditLogInfoTooltip: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 8,
|
||||
},
|
||||
|
||||
// offset the absence of the arrow icon on diff-less logs
|
||||
columnWithoutDiff: {
|
||||
marginLeft: "24px",
|
||||
},
|
||||
|
||||
fullWidth: {
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
deletedLabel: (theme) => ({
|
||||
...(theme.typography.caption as CSSObject),
|
||||
color: theme.palette.text.secondary,
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
Reference in New Issue
Block a user