Skip to content

Commit a00fdd6

Browse files
feat: Add Audit page in the UI (coder#3782)
1 parent 1359850 commit a00fdd6

23 files changed

+739
-74
lines changed

site/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"sourcemapped-stacktrace": "1.1.11",
6161
"swr": "1.3.0",
6262
"tzdata": "1.0.30",
63+
"ua-parser-js": "1.0.2",
6364
"uuid": "9.0.0",
6465
"xstate": "4.33.5",
6566
"xterm": "4.19.0",
@@ -87,6 +88,7 @@
8788
"@types/react-helmet": "6.1.5",
8889
"@types/semver": "^7.3.12",
8990
"@types/superagent": "4.1.15",
91+
"@types/ua-parser-js": "^0.7.36",
9092
"@types/uuid": "8.3.4",
9193
"@typescript-eslint/eslint-plugin": "5.36.1",
9294
"@typescript-eslint/parser": "5.36.2",

site/src/AppRouter.tsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { RequirePermission } from "components/RequirePermission/RequirePermissio
44
import { SetupPage } from "pages/SetupPage/SetupPage"
55
import { TemplateSettingsPage } from "pages/TemplateSettingsPage/TemplateSettingsPage"
66
import { FC, lazy, Suspense, useContext } from "react"
7-
import { Navigate, Route, Routes } from "react-router-dom"
7+
import { Route, Routes } from "react-router-dom"
88
import { selectPermissions } from "xServices/auth/authSelectors"
99
import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors"
1010
import { XServiceContext } from "xServices/StateContext"
@@ -139,21 +139,17 @@ export const AppRouter: FC = () => {
139139
<Route
140140
index
141141
element={
142-
process.env.NODE_ENV === "production" ? (
143-
<Navigate to="/workspaces" />
144-
) : (
145-
<AuthAndFrame>
146-
<RequirePermission
147-
isFeatureVisible={
148-
featureVisibility[FeatureNames.AuditLog] && Boolean(permissions?.viewAuditLog)
149-
}
150-
>
151-
<AuditPage />
152-
</RequirePermission>
153-
</AuthAndFrame>
154-
)
142+
<AuthAndFrame>
143+
<RequirePermission
144+
isFeatureVisible={
145+
featureVisibility[FeatureNames.AuditLog] && Boolean(permissions?.viewAuditLog)
146+
}
147+
>
148+
<AuditPage />
149+
</RequirePermission>
150+
</AuthAndFrame>
155151
}
156-
></Route>
152+
/>
157153
</Route>
158154

159155
<Route path="settings" element={<SettingsLayout />}>

site/src/api/api.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,23 @@ export const getEntitlements = async (): Promise<TypesGen.Entitlements> => {
389389
return response.data
390390
}
391391

392+
interface GetAuditLogsOptions {
393+
limit: number
394+
offset: number
395+
}
396+
397+
export const getAuditLogs = async (
398+
options: GetAuditLogsOptions,
399+
): Promise<TypesGen.AuditLogResponse> => {
400+
const response = await axios.get(`/api/v2/audit?limit=${options.limit}&offset=${options.offset}`)
401+
return response.data
402+
}
403+
404+
export const getAuditLogsCount = async (): Promise<TypesGen.AuditLogCountResponse> => {
405+
const response = await axios.get(`/api/v2/audit/count`)
406+
return response.data
407+
}
408+
392409
export const getTemplateDAUs = async (
393410
templateId: string,
394411
): Promise<TypesGen.TemplateDAUsResponse> => {
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { makeStyles } from "@material-ui/core/styles"
2+
import { AuditLog } from "api/typesGenerated"
3+
import { colors } from "theme/colors"
4+
import { MONOSPACE_FONT_FAMILY } from "theme/constants"
5+
import { combineClasses } from "util/combineClasses"
6+
7+
const getDiffValue = (value: number | string | boolean) => {
8+
if (typeof value === "string") {
9+
return `"${value}"`
10+
}
11+
12+
return value.toString()
13+
}
14+
15+
export const AuditLogDiff: React.FC<{ diff: AuditLog["diff"] }> = ({ diff }) => {
16+
const styles = useStyles()
17+
const diffEntries = Object.entries(diff)
18+
19+
return (
20+
<div className={styles.diff}>
21+
<div className={combineClasses([styles.diffColumn, styles.diffOld])}>
22+
{diffEntries.map(([attrName, valueDiff], index) => (
23+
<div key={attrName} className={styles.diffRow}>
24+
<div className={styles.diffLine}>{index + 1}</div>
25+
<div className={styles.diffIcon}>-</div>
26+
<div>
27+
{attrName}:{" "}
28+
<span className={combineClasses([styles.diffValue, styles.diffValueOld])}>
29+
{getDiffValue(valueDiff.old)}
30+
</span>
31+
</div>
32+
</div>
33+
))}
34+
</div>
35+
<div className={combineClasses([styles.diffColumn, styles.diffNew])}>
36+
{diffEntries.map(([attrName, valueDiff], index) => (
37+
<div key={attrName} className={styles.diffRow}>
38+
<div className={styles.diffLine}>{index + 1}</div>
39+
<div className={styles.diffIcon}>+</div>
40+
<div>
41+
{attrName}:{" "}
42+
<span className={combineClasses([styles.diffValue, styles.diffValueNew])}>
43+
{getDiffValue(valueDiff.new)}
44+
</span>
45+
</div>
46+
</div>
47+
))}
48+
</div>
49+
</div>
50+
)
51+
}
52+
53+
const useStyles = makeStyles((theme) => ({
54+
diff: {
55+
display: "flex",
56+
alignItems: "flex-start",
57+
fontSize: theme.typography.body2.fontSize,
58+
borderTop: `1px solid ${theme.palette.divider}`,
59+
fontFamily: MONOSPACE_FONT_FAMILY,
60+
},
61+
62+
diffColumn: {
63+
flex: 1,
64+
paddingTop: theme.spacing(2),
65+
paddingBottom: theme.spacing(2.5),
66+
lineHeight: "160%",
67+
},
68+
69+
diffOld: {
70+
backgroundColor: theme.palette.error.dark,
71+
color: theme.palette.error.contrastText,
72+
},
73+
74+
diffRow: {
75+
display: "flex",
76+
alignItems: "baseline",
77+
},
78+
79+
diffLine: {
80+
opacity: 0.5,
81+
width: theme.spacing(8),
82+
textAlign: "right",
83+
flexShrink: 0,
84+
},
85+
86+
diffIcon: {
87+
width: theme.spacing(4),
88+
textAlign: "center",
89+
fontSize: theme.typography.body1.fontSize,
90+
},
91+
92+
diffNew: {
93+
backgroundColor: theme.palette.success.dark,
94+
color: theme.palette.success.contrastText,
95+
},
96+
97+
diffValue: {
98+
padding: 1,
99+
borderRadius: theme.shape.borderRadius / 2,
100+
},
101+
102+
diffValueOld: {
103+
backgroundColor: colors.red[12],
104+
},
105+
106+
diffValueNew: {
107+
backgroundColor: colors.green[12],
108+
},
109+
}))
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Table from "@material-ui/core/Table"
2+
import TableBody from "@material-ui/core/TableBody"
3+
import TableCell from "@material-ui/core/TableCell"
4+
import TableContainer from "@material-ui/core/TableContainer"
5+
import TableHead from "@material-ui/core/TableHead"
6+
import TableRow from "@material-ui/core/TableRow"
7+
import { ComponentMeta, Story } from "@storybook/react"
8+
import { MockAuditLog, MockAuditLog2 } from "testHelpers/entities"
9+
import { AuditLogRow, AuditLogRowProps } from "./AuditLogRow"
10+
11+
export default {
12+
title: "components/AuditLogRow",
13+
component: AuditLogRow,
14+
} as ComponentMeta<typeof AuditLogRow>
15+
16+
const Template: Story<AuditLogRowProps> = (args) => (
17+
<TableContainer>
18+
<Table>
19+
<TableHead>
20+
<TableRow>
21+
<TableCell style={{ paddingLeft: 32 }}>Logs</TableCell>
22+
</TableRow>
23+
</TableHead>
24+
<TableBody>
25+
<AuditLogRow {...args} />
26+
</TableBody>
27+
</Table>
28+
</TableContainer>
29+
)
30+
31+
export const NoDiff = Template.bind({})
32+
NoDiff.args = {
33+
auditLog: MockAuditLog,
34+
}
35+
36+
export const WithDiff = Template.bind({})
37+
WithDiff.args = {
38+
auditLog: MockAuditLog2,
39+
defaultIsDiffOpen: true,
40+
}

0 commit comments

Comments
 (0)