From 720b7a5455e4e3906158fd584e50eda266ba8d32 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 7 Aug 2024 12:32:55 -0800 Subject: [PATCH 1/2] chore: remove per-org audit links For now at least, we will have the one audit page at /audit which lets you filter by organization. This also removes the need to do per-org audit permission checks. --- site/src/api/queries/organizations.ts | 19 ++++++------------ site/src/components/Filter/filter.tsx | 13 +++++------- .../dashboard/Navbar/DeploymentDropdown.tsx | 6 +----- site/src/pages/AuditPage/AuditFilter.tsx | 14 ++++++------- site/src/pages/AuditPage/AuditPage.tsx | 14 ++++--------- site/src/pages/AuditPage/AuditPageView.tsx | 16 ++------------- .../ManagementSettingsPage/SidebarView.tsx | 20 +------------------ site/src/router.tsx | 2 -- 8 files changed, 26 insertions(+), 78 deletions(-) diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index 0f3a7ae8c3c01..d4d047a446dbc 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -135,9 +135,9 @@ export const organizationPermissions = (organizationId: string | undefined) => { queryKey: ["organization", organizationId, "permissions"], queryFn: () => // Only request what we use on individual org settings, members, and group - // pages, which at the moment is whether you can edit the members or roles - // on the members page and whether you can see the create group button on - // the groups page. The edit organization check for the settings page is + // pages, which at the moment is whether you can edit the members on the + // members page, create roles on the roles page, and create groups on the + // groups page. The edit organization check for the settings page is // covered by the multi-org query at the moment, and the edit group check // on the group page is done on the group itself, not the org, so neither // show up here. @@ -185,9 +185,9 @@ export const organizationsPermissions = ( queryKey: ["organizations", organizationIds.sort(), "permissions"], queryFn: async () => { // Only request what we need for the sidebar, which is one edit permission - // per sub-link (audit, settings, groups, roles, and members pages) that - // tells us whether to show that page, since we only show them if you can - // edit (and not, at the moment if you can only view). + // per sub-link (settings, groups, roles, and members pages) that tells us + // whether to show that page, since we only show them if you can edit (and + // not, at the moment if you can only view). const checks = (organizationId: string) => ({ editMembers: { object: { @@ -210,13 +210,6 @@ export const organizationsPermissions = ( }, action: "update", }, - auditOrganization: { - object: { - resource_type: "audit_log", - organization_id: organizationId, - }, - action: "read", - }, assignOrgRole: { object: { resource_type: "assign_org_role", diff --git a/site/src/components/Filter/filter.tsx b/site/src/components/Filter/filter.tsx index c0dc5d84345af..29b34118408a4 100644 --- a/site/src/components/Filter/filter.tsx +++ b/site/src/components/Filter/filter.tsx @@ -6,6 +6,7 @@ import Divider from "@mui/material/Divider"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import Skeleton, { type SkeletonProps } from "@mui/material/Skeleton"; +import type { Breakpoint } from "@mui/system/createTheme"; import { type FC, type ReactNode, useEffect, useRef, useState } from "react"; import type { useSearchParams } from "react-router-dom"; import { @@ -142,8 +143,7 @@ type FilterProps = { error?: unknown; options?: ReactNode; presets: PresetFilter[]; - /** Set to true if there is not much horizontal space. */ - compact?: boolean; + breakpoint?: Breakpoint; }; export const Filter: FC = ({ @@ -156,7 +156,7 @@ export const Filter: FC = ({ learnMoreLabel2, learnMoreLink2, presets, - compact, + breakpoint = "md", }) => { const theme = useTheme(); // Storing local copy of the filter query so that it can be updated more @@ -187,12 +187,9 @@ export const Filter: FC = ({ display: "flex", gap: 8, marginBottom: 16, - // 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", + flexWrap: "nowrap", - [theme.breakpoints.down("md")]: { + [theme.breakpoints.down(breakpoint)]: { flexWrap: "wrap", }, }} diff --git a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx index 65e973e5672f1..f06160e71e7b6 100644 --- a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx +++ b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx @@ -124,11 +124,7 @@ const DeploymentDropdownContent: FC = ({ {canViewAuditLog && ( diff --git a/site/src/pages/AuditPage/AuditFilter.tsx b/site/src/pages/AuditPage/AuditFilter.tsx index b740148e364fa..d953e7d8f2de4 100644 --- a/site/src/pages/AuditPage/AuditFilter.tsx +++ b/site/src/pages/AuditPage/AuditFilter.tsx @@ -51,6 +51,8 @@ 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)} + breakpoint={menus.organization && "lg"} options={ <> - - - + + + {menus.organization && ( - + )} } diff --git a/site/src/pages/AuditPage/AuditPage.tsx b/site/src/pages/AuditPage/AuditPage.tsx index 34eff1cc899b0..f5f045e8e5551 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, Navigate, useLocation } from "react-router-dom"; +import { useSearchParams } from "react-router-dom"; import { paginatedAudits } from "api/queries/audits"; import { useFilter } from "components/Filter/filter"; import { useUserFilterMenu } from "components/Filter/UserFilter"; @@ -19,7 +19,6 @@ import { AuditPageView } from "./AuditPageView"; const AuditPage: FC = () => { const feats = useFeatureVisibility(); const { experiments } = useDashboard(); - const location = useLocation(); /** * There is an implicit link between auditsQuery and filter via the @@ -71,14 +70,9 @@ 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). - const canViewOrganizations = - feats.multiple_organizations && experiments.includes("multi-organization"); - if (canViewOrganizations && location.pathname !== "/deployment/audit") { - return ; - } + // With the multi-organization experiment enabled, show extra organization + // info and the organization filter dropdon. + const canViewOrganizations = experiments.includes("multi-organization"); return ( <> diff --git a/site/src/pages/AuditPage/AuditPageView.tsx b/site/src/pages/AuditPage/AuditPageView.tsx index 3bf54f6ac3bfd..c93193c823869 100644 --- a/site/src/pages/AuditPage/AuditPageView.tsx +++ b/site/src/pages/AuditPage/AuditPageView.tsx @@ -57,20 +57,8 @@ export const AuditPageView: FC = ({ const isEmpty = !isLoading && auditLogs?.length === 0; return ( - - + + {Language.title} diff --git a/site/src/pages/ManagementSettingsPage/SidebarView.tsx b/site/src/pages/ManagementSettingsPage/SidebarView.tsx index f5a31e2bce514..4417e40e3e8c0 100644 --- a/site/src/pages/ManagementSettingsPage/SidebarView.tsx +++ b/site/src/pages/ManagementSettingsPage/SidebarView.tsx @@ -11,7 +11,7 @@ import { Stack } from "components/Stack/Stack"; import { UserAvatar } from "components/UserAvatar/UserAvatar"; import { type ClassName, useClassName } from "hooks/useClassName"; import { useDashboard } from "modules/dashboard/useDashboard"; -import { linkToAuditing, linkToUsers, withFilter } from "modules/navigation"; +import { linkToUsers } from "modules/navigation"; export interface OrganizationWithPermissions extends Organization { permissions: AuthorizationResponse; @@ -133,11 +133,6 @@ const DeploymentSettingsNavigation: FC = ({ Users )} - {permissions.viewAnyAuditLog && ( - - Auditing - - )} )} @@ -264,19 +259,6 @@ const OrganizationSettingsNavigation: FC< Roles )} - {/* 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. */} - {organization.permissions.auditOrganization && ( - - Auditing - - )} )} diff --git a/site/src/router.tsx b/site/src/router.tsx index f0bcec1c3dc40..7f68576ed1911 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -389,7 +389,6 @@ export const router = createBrowserRouter( } /> } /> - } /> @@ -423,7 +422,6 @@ export const router = createBrowserRouter( } /> } /> {groupsRouter()} - } /> }> From 39defe59b8483d39200325acba98f44c55a6502c Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 7 Aug 2024 13:12:08 -0800 Subject: [PATCH 2/2] Filter audit org dropdown by auditable orgs --- site/src/pages/AuditPage/AuditFilter.tsx | 41 +++++++++++++++++------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/site/src/pages/AuditPage/AuditFilter.tsx b/site/src/pages/AuditPage/AuditFilter.tsx index d953e7d8f2de4..21cfcd12337fb 100644 --- a/site/src/pages/AuditPage/AuditFilter.tsx +++ b/site/src/pages/AuditPage/AuditFilter.tsx @@ -211,19 +211,36 @@ export const useOrganizationsFilterMenu = ({ return null; }, getOptions: async () => { - const organizationsRes = await API.getOrganizations(); - return organizationsRes.map((organization) => ({ - label: organization.display_name || organization.name, - value: organization.name, - startIcon: ( - + // Only show the organizations for which you can view audit logs. + const organizations = await API.getOrganizations(); + const permissions = await API.checkAuthorization({ + checks: Object.fromEntries( + organizations.map((organization) => [ + organization.id, + { + object: { + resource_type: "audit_log", + organization_id: organization.id, + }, + action: "read", + }, + ]), ), - })); + }); + return organizations + .filter((organization) => permissions[organization.id]) + .map((organization) => ({ + label: organization.display_name || organization.name, + value: organization.name, + startIcon: ( + + ), + })); }, }); };