diff --git a/site/src/components/Filter/SelectFilter.tsx b/site/src/components/Filter/SelectFilter.tsx index e679c3fbd7572..77f7819e9ead9 100644 --- a/site/src/components/Filter/SelectFilter.tsx +++ b/site/src/components/Filter/SelectFilter.tsx @@ -52,7 +52,7 @@ export const SelectFilter: FC = ({ {selectedOption?.label ?? placeholder} diff --git a/site/src/components/Filter/filter.tsx b/site/src/components/Filter/filter.tsx index b26ce444a805f..c0dc5d84345af 100644 --- a/site/src/components/Filter/filter.tsx +++ b/site/src/components/Filter/filter.tsx @@ -142,6 +142,8 @@ type FilterProps = { error?: unknown; options?: ReactNode; presets: PresetFilter[]; + /** Set to true if there is not much horizontal space. */ + compact?: boolean; }; export const Filter: FC = ({ @@ -154,6 +156,7 @@ export const Filter: FC = ({ learnMoreLabel2, learnMoreLink2, presets, + compact, }) => { const theme = useTheme(); // Storing local copy of the filter query so that it can be updated more @@ -184,7 +187,10 @@ export const Filter: FC = ({ display: "flex", gap: 8, marginBottom: 16, - flexWrap: "nowrap", + // For now compact just means immediately wrapping, but maybe we should + // have a collapsible section or consolidate into one menu or something. + // TODO: Remove separate compact mode once multi-org is stable. + flexWrap: compact ? "wrap" : "nowrap", [theme.breakpoints.down("md")]: { flexWrap: "wrap", diff --git a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx index 4a79bc00c1931..ca71b1db95f12 100644 --- a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx +++ b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx @@ -10,7 +10,7 @@ import { PopoverTrigger, usePopover, } from "components/Popover/Popover"; -import { USERS_LINK } from "modules/navigation"; +import { AUDIT_LINK, USERS_LINK } from "modules/navigation"; interface DeploymentDropdownProps { canViewDeployment: boolean; @@ -114,7 +114,7 @@ const DeploymentDropdownContent: FC = ({ {canViewAllUsers && ( @@ -124,7 +124,7 @@ const DeploymentDropdownContent: FC = ({ {canViewAuditLog && ( diff --git a/site/src/modules/navigation.ts b/site/src/modules/navigation.ts index 74217a4ceaaac..a659226ecc727 100644 --- a/site/src/modules/navigation.ts +++ b/site/src/modules/navigation.ts @@ -2,6 +2,10 @@ * @fileoverview TODO: centralize navigation code here! URL constants, URL formatting, all of it */ -export const USERS_LINK = `/users?filter=${encodeURIComponent( - "status:active", -)}`; +export function withFilter(path: string, filter: string) { + return path + (filter ? `?filter=${encodeURIComponent(filter)}` : ""); +} + +export const AUDIT_LINK = "/audit"; + +export const USERS_LINK = withFilter("/users", "status:active"); diff --git a/site/src/pages/AuditPage/AuditFilter.tsx b/site/src/pages/AuditPage/AuditFilter.tsx index 01a38a1c5077f..b740148e364fa 100644 --- a/site/src/pages/AuditPage/AuditFilter.tsx +++ b/site/src/pages/AuditPage/AuditFilter.tsx @@ -51,8 +51,6 @@ interface AuditFilterProps { } export const AuditFilter: FC = ({ filter, error, menus }) => { - // Use a smaller width if including the organization filter. - const width = menus.organization && 175; return ( = ({ filter, error, menus }) => { isLoading={menus.user.isInitializing} filter={filter} error={error} + // There is not much space with the sidebar and four filters, so in this + // case we will use the compact mode. + compact={Boolean(menus.organization)} options={ <> - - - + + + {menus.organization && ( - + )} } diff --git a/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.stories.tsx b/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.stories.tsx index 352b6b8d578aa..55451aa51c75c 100644 --- a/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.stories.tsx +++ b/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.stories.tsx @@ -113,3 +113,10 @@ export const SecretDiffValue: Story = { auditLog: MockAuditLogGitSSH, }, }; + +export const WithOrganization: Story = { + args: { + auditLog: MockAuditLog, + showOrgDetails: true, + }, +}; diff --git a/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx b/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx index 5cd7a26120a25..9c243c95a318e 100644 --- a/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx +++ b/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx @@ -1,7 +1,9 @@ import type { CSSObject, Interpolation, Theme } from "@emotion/react"; +import InfoOutlined from "@mui/icons-material/InfoOutlined"; import Collapse from "@mui/material/Collapse"; import Link from "@mui/material/Link"; import TableCell from "@mui/material/TableCell"; +import Tooltip from "@mui/material/Tooltip"; import { type FC, useState } from "react"; import { Link as RouterLink } from "react-router-dom"; import userAgentParser from "ua-parser-js"; @@ -115,42 +117,80 @@ export const AuditLogRow: FC = ({ - - {auditLog.ip && ( - - <>IP: - {auditLog.ip} - - )} - {os.name && ( - - <>OS: - {os.name} - - )} - {browser.name && ( - - <>Browser: - - {browser.name} {browser.version} - - - )} - {showOrgDetails && auditLog.organization && ( - - <>Org: - + {/* With multi-org, there is not enough space so show + everything in a tooltip. */} + {showOrgDetails ? ( + + {auditLog.ip && ( +
+

IP:

+
{auditLog.ip}
+
+ )} + {os.name && ( +
+

OS:

+
{os.name}
+
+ )} + {browser.name && ( +
+

Browser:

+
+ {browser.name} {browser.version} +
+
+ )} + {auditLog.organization && ( +
+

+ Organization: +

+ + {auditLog.organization.display_name || + auditLog.organization.name} + +
+ )} + + } + > + ({ + fontSize: 20, + color: theme.palette.info.light, + })} + /> +
+ ) : ( + + {auditLog.ip && ( + + IP: + {auditLog.ip} + + )} + {os.name && ( + + OS: + {os.name} + + )} + {browser.name && ( + + Browser: - {auditLog.organization.display_name || - auditLog.organization.name} + {browser.name} {browser.version} - - - )} - +
+ )} +
+ )} ({ + 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", diff --git a/site/src/pages/AuditPage/AuditPage.tsx b/site/src/pages/AuditPage/AuditPage.tsx index 81394ac72a4b3..ed81e36f19ded 100644 --- a/site/src/pages/AuditPage/AuditPage.tsx +++ b/site/src/pages/AuditPage/AuditPage.tsx @@ -1,6 +1,6 @@ import type { FC } from "react"; import { Helmet } from "react-helmet-async"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams, Navigate, useLocation } from "react-router-dom"; import { paginatedAudits } from "api/queries/audits"; import { useFilter } from "components/Filter/filter"; import { useUserFilterMenu } from "components/Filter/UserFilter"; @@ -19,6 +19,8 @@ import { AuditPageView } from "./AuditPageView"; const AuditPage: FC = () => { const { audit_log: isAuditLogVisible } = useFeatureVisibility(); const { experiments } = useDashboard(); + const location = useLocation(); + const isMultiOrg = experiments.includes("multi-organization"); /** * There is an implicit link between auditsQuery and filter via the @@ -70,6 +72,13 @@ const AuditPage: FC = () => { }), }); + // TODO: Once multi-org is stable, we should place this redirect into the + // router directly, if we still need to maintain it (for users who are + // typing the old URL manually or have it bookmarked). + if (isMultiOrg && location.pathname !== "/deployment/audit") { + return ; + } + return ( <> @@ -82,7 +91,7 @@ const AuditPage: FC = () => { isAuditLogVisible={isAuditLogVisible} auditsQuery={auditsQuery} error={auditsQuery.error} - showOrgDetails={experiments.includes("multi-organization")} + showOrgDetails={isMultiOrg} filterProps={{ filter, error: auditsQuery.error, @@ -90,9 +99,7 @@ const AuditPage: FC = () => { user: userMenu, action: actionMenu, resourceType: resourceTypeMenu, - organization: experiments.includes("multi-organization") - ? organizationsMenu - : undefined, + organization: isMultiOrg ? organizationsMenu : undefined, }, }} /> diff --git a/site/src/pages/AuditPage/AuditPageView.tsx b/site/src/pages/AuditPage/AuditPageView.tsx index c93193c823869..3bf54f6ac3bfd 100644 --- a/site/src/pages/AuditPage/AuditPageView.tsx +++ b/site/src/pages/AuditPage/AuditPageView.tsx @@ -57,8 +57,20 @@ export const AuditPageView: FC = ({ const isEmpty = !isLoading && auditLogs?.length === 0; return ( - - + + {Language.title} diff --git a/site/src/pages/ManagementSettingsPage/Sidebar.tsx b/site/src/pages/ManagementSettingsPage/Sidebar.tsx index a07da66570897..5a18c5657797d 100644 --- a/site/src/pages/ManagementSettingsPage/Sidebar.tsx +++ b/site/src/pages/ManagementSettingsPage/Sidebar.tsx @@ -10,7 +10,7 @@ import { Stack } from "components/Stack/Stack"; import { UserAvatar } from "components/UserAvatar/UserAvatar"; import { type ClassName, useClassName } from "hooks/useClassName"; import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; -import { USERS_LINK } from "modules/navigation"; +import { AUDIT_LINK, USERS_LINK, withFilter } from "modules/navigation"; import { useOrganizationSettings } from "./ManagementSettingsLayout"; export const Sidebar: FC = () => { @@ -103,6 +103,9 @@ const DeploymentSettingsNavigation: FC = ({ {!organizationsEnabled && ( Groups )} + + Auditing + )} @@ -148,8 +151,14 @@ export const OrganizationSettingsNavigation: FC< Groups + {/* For now redirect to the site-wide audit page with the organization + pre-filled into the filter. Based on user feedback we might want + to serve a copy of the audit page or even delete this link. */} Auditing diff --git a/site/src/router.tsx b/site/src/router.tsx index 3d54613fb98dd..dea9995f52882 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -406,6 +406,7 @@ export const router = createBrowserRouter( } /> } /> {groupsRouter()} + } /> }>