diff --git a/site/e2e/tests/auditLogs.spec.ts b/site/e2e/tests/auditLogs.spec.ts index 8afb2e714c..31d3208c63 100644 --- a/site/e2e/tests/auditLogs.spec.ts +++ b/site/e2e/tests/auditLogs.spec.ts @@ -35,7 +35,6 @@ test("logins are logged", async ({ page }) => { await page.goto("/audit"); const username = users.auditor.username; - const user = currentUser(page); const loginMessage = `${username} logged in`; // Make sure those things we did all actually show up await resetSearch(page, username); diff --git a/site/src/pages/GroupsPage/GroupsPage.tsx b/site/src/pages/GroupsPage/GroupsPage.tsx index a99ec44334..d5ef810f9f 100644 --- a/site/src/pages/GroupsPage/GroupsPage.tsx +++ b/site/src/pages/GroupsPage/GroupsPage.tsx @@ -2,7 +2,6 @@ import GroupAdd from "@mui/icons-material/GroupAddOutlined"; import { getErrorMessage } from "api/errors"; import { groupsByOrganization } from "api/queries/groups"; import { organizationsPermissions } from "api/queries/organizations"; -import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Button } from "components/Button/Button"; import { EmptyState } from "components/EmptyState/EmptyState"; import { displayError } from "components/GlobalSnackbar/utils"; @@ -10,6 +9,7 @@ import { Loader } from "components/Loader/Loader"; import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; import { Stack } from "components/Stack/Stack"; import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; +import { RequirePermission } from "modules/permissions/RequirePermission"; import { type FC, useEffect } from "react"; import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; @@ -54,16 +54,26 @@ export const GroupsPage: FC = () => { return ; } + const helmet = ( + + {pageTitle("Groups")} + + ); + const permissions = permissionsQuery.data?.[organization.id]; - if (!permissions) { - return ; + + if (!permissions?.viewGroups) { + return ( + <> + {helmet} + + + ); } return ( <> - - {pageTitle("Groups")} - + {helmet} { const { organization: organizationName } = useParams() as { organization: string; }; - const { organizationPermissions } = useOrganizationSettings(); + const { organization, organizationPermissions } = useOrganizationSettings(); const [roleToDelete, setRoleToDelete] = useState(); @@ -49,65 +49,67 @@ export const CustomRolesPage: FC = () => { } }, [organizationRolesQuery.error]); - if (!organizationPermissions) { - return ; + if (!organization) { + return ; } return ( - + <> - {pageTitle("Custom Roles")} + + {pageTitle( + "Custom Roles", + organization.display_name || organization.name, + )} + - - - + + + + - - - - setRoleToDelete(undefined)} - onConfirm={async () => { - try { - if (roleToDelete) { - await deleteRoleMutation.mutateAsync(roleToDelete.name); + setRoleToDelete(undefined)} + onConfirm={async () => { + try { + if (roleToDelete) { + await deleteRoleMutation.mutateAsync(roleToDelete.name); + } + setRoleToDelete(undefined); + await organizationRolesQuery.refetch(); + displaySuccess("Custom role deleted successfully!"); + } catch (error) { + displayError( + getErrorMessage(error, "Failed to delete custom role"), + ); } - setRoleToDelete(undefined); - await organizationRolesQuery.refetch(); - displaySuccess("Custom role deleted successfully!"); - } catch (error) { - displayError( - getErrorMessage(error, "Failed to delete custom role"), - ); - } - }} - /> - + }} + /> + + ); }; diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx index 91d138ed26..613572348a 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx @@ -16,6 +16,7 @@ import { Link } from "components/Link/Link"; import { Paywall } from "components/Paywall/Paywall"; import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; +import { RequirePermission } from "modules/permissions/RequirePermission"; import { type FC, useEffect, useState } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQueries, useQuery, useQueryClient } from "react-query"; @@ -31,8 +32,7 @@ export const IdpSyncPage: FC = () => { const { organization: organizationName } = useParams() as { organization: string; }; - const { organizations } = useOrganizationSettings(); - const organization = organizations?.find((o) => o.name === organizationName); + const { organization, organizationPermissions } = useOrganizationSettings(); const [groupField, setGroupField] = useState(""); const [roleField, setRoleField] = useState(""); @@ -80,6 +80,23 @@ export const IdpSyncPage: FC = () => { return ; } + const helmet = ( + + + {pageTitle("IdP Sync", organization.display_name || organization.name)} + + + ); + + if (!organizationPermissions?.viewIdpSyncSettings) { + return ( + <> + {helmet} + + + ); + } + const patchGroupSyncSettingsMutation = useMutation( patchGroupSyncSettings(organizationName, queryClient), ); @@ -103,9 +120,7 @@ export const IdpSyncPage: FC = () => { return ( <> - - {pageTitle("IdP Sync")} - + {helmet}
diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx index 7ae0eb72be..ffa7b08b83 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx @@ -15,6 +15,7 @@ import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; import { Stack } from "components/Stack/Stack"; import { useAuthenticated } from "contexts/auth/RequireAuth"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; +import { RequirePermission } from "modules/permissions/RequirePermission"; import { type FC, useState } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQuery, useQueryClient } from "react-query"; @@ -54,7 +55,7 @@ const OrganizationMembersPage: FC = () => { const [memberToDelete, setMemberToDelete] = useState(); - if (!organization || !organizationPermissions) { + if (!organization) { return ; } @@ -66,6 +67,15 @@ const OrganizationMembersPage: FC = () => { ); + if (!organizationPermissions) { + return ( + <> + {helmet} + + + ); + } + return ( <> {helmet} diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage.tsx index 5a4965c039..fc736975c0 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage.tsx @@ -4,6 +4,7 @@ import { EmptyState } from "components/EmptyState/EmptyState"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useDashboard } from "modules/dashboard/useDashboard"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; +import { RequirePermission } from "modules/permissions/RequirePermission"; import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; @@ -15,7 +16,7 @@ const OrganizationProvisionersPage: FC = () => { const { organization: organizationName } = useParams() as { organization: string; }; - const { organization } = useOrganizationSettings(); + const { organization, organizationPermissions } = useOrganizationSettings(); const { entitlements } = useDashboard(); const { metadata } = useEmbeddedMetadata(); const buildInfoQuery = useQuery(buildInfo(metadata["build-info"])); @@ -25,16 +26,29 @@ const OrganizationProvisionersPage: FC = () => { return ; } + const helmet = ( + + + {pageTitle( + "Provisioners", + organization.display_name || organization.name, + )} + + + ); + + if (!organizationPermissions?.viewProvisioners) { + return ( + <> + {helmet} + + + ); + } + return ( <> - - - {pageTitle( - "Provisioners", - organization.display_name || organization.name, - )} - - + {helmet} { @@ -24,36 +27,58 @@ const OrganizationSettingsPage: FC = () => { deleteOrganization(queryClient), ); - if (!organization || !organizationPermissions?.editSettings) { + if (!organization) { return ; } + const helmet = ( + + + {pageTitle("Settings", organization.display_name || organization.name)} + + + ); + + if (!organizationPermissions?.editSettings) { + return ( + <> + {helmet} + + + ); + } + const error = updateOrganizationMutation.error ?? deleteOrganizationMutation.error; return ( - { - const updatedOrganization = - await updateOrganizationMutation.mutateAsync({ - organizationId: organization.id, - req: values, - }); - navigate(`/organizations/${updatedOrganization.name}/settings`); - displaySuccess("Organization settings updated."); - }} - onDeleteOrganization={async () => { - try { - await deleteOrganizationMutation.mutateAsync(organization.id); - displaySuccess("Organization deleted"); - navigate("/organizations"); - } catch (error) { - displayError(getErrorMessage(error, "Failed to delete organization")); - } - }} - /> + <> + {helmet} + { + const updatedOrganization = + await updateOrganizationMutation.mutateAsync({ + organizationId: organization.id, + req: values, + }); + navigate(`/organizations/${updatedOrganization.name}/settings`); + displaySuccess("Organization settings updated."); + }} + onDeleteOrganization={async () => { + try { + await deleteOrganizationMutation.mutateAsync(organization.id); + displaySuccess("Organization deleted"); + navigate("/organizations"); + } catch (error) { + displayError( + getErrorMessage(error, "Failed to delete organization"), + ); + } + }} + /> + ); }; diff --git a/site/src/pages/OrganizationSettingsPage/ProvisionersPage/ProvisionersPage.tsx b/site/src/pages/OrganizationSettingsPage/ProvisionersPage/ProvisionersPage.tsx index 051f916c3a..ced95a95e0 100644 --- a/site/src/pages/OrganizationSettingsPage/ProvisionersPage/ProvisionersPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/ProvisionersPage/ProvisionersPage.tsx @@ -2,6 +2,7 @@ import { EmptyState } from "components/EmptyState/EmptyState"; import { TabLink, Tabs, TabsList } from "components/Tabs/Tabs"; import { useSearchParamsKey } from "hooks/useSearchParamsKey"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; +import { RequirePermission } from "modules/permissions/RequirePermission"; import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { pageTitle } from "utils/page"; @@ -16,26 +17,32 @@ const ProvisionersPage: FC = () => { }); if (!organization || !organizationPermissions?.viewProvisionerJobs) { + return ; + } + + const helmet = ( + + + {pageTitle( + "Provisioners", + organization.display_name || organization.name, + )} + + + ); + + if (!organizationPermissions?.viewProvisioners) { return ( <> - - {pageTitle("Provisioners")} - - + {helmet} + ); } return ( <> - - - {pageTitle( - "Provisioners", - organization.display_name || organization.name, - )} - - + {helmet}
diff --git a/site/src/pages/TemplatePage/TemplatePageHeader.tsx b/site/src/pages/TemplatePage/TemplatePageHeader.tsx index b04a2c6d10..7bb1d9e54a 100644 --- a/site/src/pages/TemplatePage/TemplatePageHeader.tsx +++ b/site/src/pages/TemplatePage/TemplatePageHeader.tsx @@ -168,7 +168,6 @@ export const TemplatePageHeader: FC = ({ onDeleteTemplate, }) => { const getLink = useLinks(); - const hasIcon = template.icon && template.icon !== ""; const templateLink = getLink( linkToTemplate(template.organization_name, template.name), ); diff --git a/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx b/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx index 5cb1e4fdde..845918a7b7 100644 --- a/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx +++ b/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx @@ -110,25 +110,6 @@ interface ExternalAuthRowProps { onValidateExternalAuth: () => void; } -const StyledBadge = styled(Badge)(({ theme }) => ({ - "& .MuiBadge-badge": { - // Make a circular background for the icon. Background provides contrast, with a thin - // border to separate it from the avatar image. - backgroundColor: `${theme.palette.background.paper}`, - borderStyle: "solid", - borderColor: `${theme.palette.secondary.main}`, - borderWidth: "thin", - - // Override the default minimum sizes, as they are larger than what we want. - minHeight: "0px", - minWidth: "0px", - // Override the default "height", which is usually set to some constant value. - height: "auto", - // Padding adds some room for the icon to live in. - padding: "0.1em", - }, -})); - const ExternalAuthRow: FC = ({ app, unlinked, diff --git a/site/src/pages/UserSettingsPage/Sidebar.tsx b/site/src/pages/UserSettingsPage/Sidebar.tsx index 5cc8c54dcb..69d51ae3bb 100644 --- a/site/src/pages/UserSettingsPage/Sidebar.tsx +++ b/site/src/pages/UserSettingsPage/Sidebar.tsx @@ -22,7 +22,7 @@ interface SidebarProps { } export const Sidebar: FC = ({ user }) => { - const { entitlements, experiments } = useDashboard(); + const { entitlements } = useDashboard(); const showSchedulePage = entitlements.features.advanced_template_scheduling.enabled; diff --git a/site/src/pages/UsersPage/UsersPage.tsx b/site/src/pages/UsersPage/UsersPage.tsx index 9d2aaadefc..c8677e3a44 100644 --- a/site/src/pages/UsersPage/UsersPage.tsx +++ b/site/src/pages/UsersPage/UsersPage.tsx @@ -23,7 +23,7 @@ import { useDashboard } from "modules/dashboard/useDashboard"; import { type FC, useState } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQuery, useQueryClient } from "react-query"; -import { useLocation, useNavigate, useSearchParams } from "react-router-dom"; +import { useNavigate, useSearchParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import { generateRandomString } from "utils/random"; import { ResetPasswordDialog } from "./ResetPasswordDialog"; @@ -39,7 +39,6 @@ type UserPageProps = { const UsersPage: FC = ({ defaultNewPassword }) => { const queryClient = useQueryClient(); const navigate = useNavigate(); - const location = useLocation(); const searchParamsResult = useSearchParams(); const { entitlements } = useDashboard(); const [searchParams] = searchParamsResult;