mirror of
https://github.com/coder/coder.git
synced 2026-10-29 17:44:33 +08:00
fix: improve permissions checks in organization settings (#16849)
This commit is contained in:
@@ -35,7 +35,6 @@ test("logins are logged", async ({ page }) => {
|
|||||||
await page.goto("/audit");
|
await page.goto("/audit");
|
||||||
const username = users.auditor.username;
|
const username = users.auditor.username;
|
||||||
|
|
||||||
const user = currentUser(page);
|
|
||||||
const loginMessage = `${username} logged in`;
|
const loginMessage = `${username} logged in`;
|
||||||
// Make sure those things we did all actually show up
|
// Make sure those things we did all actually show up
|
||||||
await resetSearch(page, username);
|
await resetSearch(page, username);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import GroupAdd from "@mui/icons-material/GroupAddOutlined";
|
|||||||
import { getErrorMessage } from "api/errors";
|
import { getErrorMessage } from "api/errors";
|
||||||
import { groupsByOrganization } from "api/queries/groups";
|
import { groupsByOrganization } from "api/queries/groups";
|
||||||
import { organizationsPermissions } from "api/queries/organizations";
|
import { organizationsPermissions } from "api/queries/organizations";
|
||||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
|
||||||
import { Button } from "components/Button/Button";
|
import { Button } from "components/Button/Button";
|
||||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||||
import { displayError } from "components/GlobalSnackbar/utils";
|
import { displayError } from "components/GlobalSnackbar/utils";
|
||||||
@@ -10,6 +9,7 @@ import { Loader } from "components/Loader/Loader";
|
|||||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||||
|
import { RequirePermission } from "modules/permissions/RequirePermission";
|
||||||
import { type FC, useEffect } from "react";
|
import { type FC, useEffect } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
@@ -54,16 +54,26 @@ export const GroupsPage: FC = () => {
|
|||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const helmet = (
|
||||||
|
<Helmet>
|
||||||
|
<title>{pageTitle("Groups")}</title>
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
|
||||||
const permissions = permissionsQuery.data?.[organization.id];
|
const permissions = permissionsQuery.data?.[organization.id];
|
||||||
if (!permissions) {
|
|
||||||
return <ErrorAlert error={permissionsQuery.error} />;
|
if (!permissions?.viewGroups) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{helmet}
|
||||||
|
<RequirePermission isFeatureVisible={false} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
{helmet}
|
||||||
<title>{pageTitle("Groups")}</title>
|
|
||||||
</Helmet>
|
|
||||||
|
|
||||||
<Stack
|
<Stack
|
||||||
alignItems="baseline"
|
alignItems="baseline"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { getErrorMessage } from "api/errors";
|
|||||||
import { deleteOrganizationRole, organizationRoles } from "api/queries/roles";
|
import { deleteOrganizationRole, organizationRoles } from "api/queries/roles";
|
||||||
import type { Role } from "api/typesGenerated";
|
import type { Role } from "api/typesGenerated";
|
||||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
||||||
|
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||||
import { Loader } from "components/Loader/Loader";
|
|
||||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||||
@@ -22,7 +22,7 @@ export const CustomRolesPage: FC = () => {
|
|||||||
const { organization: organizationName } = useParams() as {
|
const { organization: organizationName } = useParams() as {
|
||||||
organization: string;
|
organization: string;
|
||||||
};
|
};
|
||||||
const { organizationPermissions } = useOrganizationSettings();
|
const { organization, organizationPermissions } = useOrganizationSettings();
|
||||||
|
|
||||||
const [roleToDelete, setRoleToDelete] = useState<Role>();
|
const [roleToDelete, setRoleToDelete] = useState<Role>();
|
||||||
|
|
||||||
@@ -49,65 +49,67 @@ export const CustomRolesPage: FC = () => {
|
|||||||
}
|
}
|
||||||
}, [organizationRolesQuery.error]);
|
}, [organizationRolesQuery.error]);
|
||||||
|
|
||||||
if (!organizationPermissions) {
|
if (!organization) {
|
||||||
return <Loader />;
|
return <EmptyState message="Organization not found" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RequirePermission
|
<>
|
||||||
isFeatureVisible={
|
|
||||||
organizationPermissions.assignOrgRoles ||
|
|
||||||
organizationPermissions.createOrgRoles ||
|
|
||||||
organizationPermissions.viewOrgRoles
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{pageTitle("Custom Roles")}</title>
|
<title>
|
||||||
|
{pageTitle(
|
||||||
|
"Custom Roles",
|
||||||
|
organization.display_name || organization.name,
|
||||||
|
)}
|
||||||
|
</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
<RequirePermission
|
||||||
<Stack
|
isFeatureVisible={organizationPermissions?.viewOrgRoles ?? false}
|
||||||
alignItems="baseline"
|
|
||||||
direction="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
>
|
>
|
||||||
<SettingsHeader
|
<Stack
|
||||||
title="Roles"
|
alignItems="baseline"
|
||||||
description="Manage roles for this organization."
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<SettingsHeader
|
||||||
|
title="Roles"
|
||||||
|
description="Manage roles for this organization."
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<CustomRolesPageView
|
||||||
|
builtInRoles={builtInRoles}
|
||||||
|
customRoles={customRoles}
|
||||||
|
onDeleteRole={setRoleToDelete}
|
||||||
|
canAssignOrgRole={organizationPermissions?.assignOrgRoles ?? false}
|
||||||
|
canCreateOrgRole={organizationPermissions?.createOrgRoles ?? false}
|
||||||
|
isCustomRolesEnabled={isCustomRolesEnabled}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<CustomRolesPageView
|
<DeleteDialog
|
||||||
builtInRoles={builtInRoles}
|
key={roleToDelete?.name}
|
||||||
customRoles={customRoles}
|
isOpen={roleToDelete !== undefined}
|
||||||
onDeleteRole={setRoleToDelete}
|
confirmLoading={deleteRoleMutation.isLoading}
|
||||||
canAssignOrgRole={organizationPermissions.assignOrgRoles}
|
name={roleToDelete?.name ?? ""}
|
||||||
canCreateOrgRole={organizationPermissions.createOrgRoles}
|
entity="role"
|
||||||
isCustomRolesEnabled={isCustomRolesEnabled}
|
onCancel={() => setRoleToDelete(undefined)}
|
||||||
/>
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
<DeleteDialog
|
if (roleToDelete) {
|
||||||
key={roleToDelete?.name}
|
await deleteRoleMutation.mutateAsync(roleToDelete.name);
|
||||||
isOpen={roleToDelete !== undefined}
|
}
|
||||||
confirmLoading={deleteRoleMutation.isLoading}
|
setRoleToDelete(undefined);
|
||||||
name={roleToDelete?.name ?? ""}
|
await organizationRolesQuery.refetch();
|
||||||
entity="role"
|
displaySuccess("Custom role deleted successfully!");
|
||||||
onCancel={() => setRoleToDelete(undefined)}
|
} catch (error) {
|
||||||
onConfirm={async () => {
|
displayError(
|
||||||
try {
|
getErrorMessage(error, "Failed to delete custom role"),
|
||||||
if (roleToDelete) {
|
);
|
||||||
await deleteRoleMutation.mutateAsync(roleToDelete.name);
|
|
||||||
}
|
}
|
||||||
setRoleToDelete(undefined);
|
}}
|
||||||
await organizationRolesQuery.refetch();
|
/>
|
||||||
displaySuccess("Custom role deleted successfully!");
|
</RequirePermission>
|
||||||
} catch (error) {
|
</>
|
||||||
displayError(
|
|
||||||
getErrorMessage(error, "Failed to delete custom role"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</RequirePermission>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { Link } from "components/Link/Link";
|
|||||||
import { Paywall } from "components/Paywall/Paywall";
|
import { Paywall } from "components/Paywall/Paywall";
|
||||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||||
|
import { RequirePermission } from "modules/permissions/RequirePermission";
|
||||||
import { type FC, useEffect, useState } from "react";
|
import { type FC, useEffect, useState } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useMutation, useQueries, useQuery, useQueryClient } from "react-query";
|
import { useMutation, useQueries, useQuery, useQueryClient } from "react-query";
|
||||||
@@ -31,8 +32,7 @@ export const IdpSyncPage: FC = () => {
|
|||||||
const { organization: organizationName } = useParams() as {
|
const { organization: organizationName } = useParams() as {
|
||||||
organization: string;
|
organization: string;
|
||||||
};
|
};
|
||||||
const { organizations } = useOrganizationSettings();
|
const { organization, organizationPermissions } = useOrganizationSettings();
|
||||||
const organization = organizations?.find((o) => o.name === organizationName);
|
|
||||||
const [groupField, setGroupField] = useState("");
|
const [groupField, setGroupField] = useState("");
|
||||||
const [roleField, setRoleField] = useState("");
|
const [roleField, setRoleField] = useState("");
|
||||||
|
|
||||||
@@ -80,6 +80,23 @@ export const IdpSyncPage: FC = () => {
|
|||||||
return <EmptyState message="Organization not found" />;
|
return <EmptyState message="Organization not found" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const helmet = (
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{pageTitle("IdP Sync", organization.display_name || organization.name)}
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!organizationPermissions?.viewIdpSyncSettings) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{helmet}
|
||||||
|
<RequirePermission isFeatureVisible={false} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const patchGroupSyncSettingsMutation = useMutation(
|
const patchGroupSyncSettingsMutation = useMutation(
|
||||||
patchGroupSyncSettings(organizationName, queryClient),
|
patchGroupSyncSettings(organizationName, queryClient),
|
||||||
);
|
);
|
||||||
@@ -103,9 +120,7 @@ export const IdpSyncPage: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
{helmet}
|
||||||
<title>{pageTitle("IdP Sync")}</title>
|
|
||||||
</Helmet>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-12">
|
<div className="flex flex-col gap-12">
|
||||||
<header className="flex flex-row items-baseline justify-between">
|
<header className="flex flex-row items-baseline justify-between">
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
|||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||||
|
import { RequirePermission } from "modules/permissions/RequirePermission";
|
||||||
import { type FC, useState } from "react";
|
import { type FC, useState } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||||
@@ -54,7 +55,7 @@ const OrganizationMembersPage: FC = () => {
|
|||||||
const [memberToDelete, setMemberToDelete] =
|
const [memberToDelete, setMemberToDelete] =
|
||||||
useState<OrganizationMemberWithUserData>();
|
useState<OrganizationMemberWithUserData>();
|
||||||
|
|
||||||
if (!organization || !organizationPermissions) {
|
if (!organization) {
|
||||||
return <EmptyState message="Organization not found" />;
|
return <EmptyState message="Organization not found" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +67,15 @@ const OrganizationMembersPage: FC = () => {
|
|||||||
</Helmet>
|
</Helmet>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!organizationPermissions) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{helmet}
|
||||||
|
<RequirePermission isFeatureVisible={false} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{helmet}
|
{helmet}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { EmptyState } from "components/EmptyState/EmptyState";
|
|||||||
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
|
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
|
||||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||||
|
import { RequirePermission } from "modules/permissions/RequirePermission";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
@@ -15,7 +16,7 @@ const OrganizationProvisionersPage: FC = () => {
|
|||||||
const { organization: organizationName } = useParams() as {
|
const { organization: organizationName } = useParams() as {
|
||||||
organization: string;
|
organization: string;
|
||||||
};
|
};
|
||||||
const { organization } = useOrganizationSettings();
|
const { organization, organizationPermissions } = useOrganizationSettings();
|
||||||
const { entitlements } = useDashboard();
|
const { entitlements } = useDashboard();
|
||||||
const { metadata } = useEmbeddedMetadata();
|
const { metadata } = useEmbeddedMetadata();
|
||||||
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
|
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
|
||||||
@@ -25,16 +26,29 @@ const OrganizationProvisionersPage: FC = () => {
|
|||||||
return <EmptyState message="Organization not found" />;
|
return <EmptyState message="Organization not found" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const helmet = (
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{pageTitle(
|
||||||
|
"Provisioners",
|
||||||
|
organization.display_name || organization.name,
|
||||||
|
)}
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!organizationPermissions?.viewProvisioners) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{helmet}
|
||||||
|
<RequirePermission isFeatureVisible={false} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
{helmet}
|
||||||
<title>
|
|
||||||
{pageTitle(
|
|
||||||
"Provisioners",
|
|
||||||
organization.display_name || organization.name,
|
|
||||||
)}
|
|
||||||
</title>
|
|
||||||
</Helmet>
|
|
||||||
<OrganizationProvisionersPageView
|
<OrganizationProvisionersPageView
|
||||||
showPaywall={!entitlements.features.multiple_organizations.enabled}
|
showPaywall={!entitlements.features.multiple_organizations.enabled}
|
||||||
error={provisionersQuery.error}
|
error={provisionersQuery.error}
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ import { EmptyState } from "components/EmptyState/EmptyState";
|
|||||||
import { displaySuccess } from "components/GlobalSnackbar/utils";
|
import { displaySuccess } from "components/GlobalSnackbar/utils";
|
||||||
import { displayError } from "components/GlobalSnackbar/utils";
|
import { displayError } from "components/GlobalSnackbar/utils";
|
||||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||||
|
import { RequirePermission } from "modules/permissions/RequirePermission";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "react-query";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { pageTitle } from "utils/page";
|
||||||
import { OrganizationSettingsPageView } from "./OrganizationSettingsPageView";
|
import { OrganizationSettingsPageView } from "./OrganizationSettingsPageView";
|
||||||
|
|
||||||
const OrganizationSettingsPage: FC = () => {
|
const OrganizationSettingsPage: FC = () => {
|
||||||
@@ -24,36 +27,58 @@ const OrganizationSettingsPage: FC = () => {
|
|||||||
deleteOrganization(queryClient),
|
deleteOrganization(queryClient),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!organization || !organizationPermissions?.editSettings) {
|
if (!organization) {
|
||||||
return <EmptyState message="Organization not found" />;
|
return <EmptyState message="Organization not found" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const helmet = (
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{pageTitle("Settings", organization.display_name || organization.name)}
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!organizationPermissions?.editSettings) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{helmet}
|
||||||
|
<RequirePermission isFeatureVisible={false} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const error =
|
const error =
|
||||||
updateOrganizationMutation.error ?? deleteOrganizationMutation.error;
|
updateOrganizationMutation.error ?? deleteOrganizationMutation.error;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrganizationSettingsPageView
|
<>
|
||||||
organization={organization}
|
{helmet}
|
||||||
error={error}
|
<OrganizationSettingsPageView
|
||||||
onSubmit={async (values) => {
|
organization={organization}
|
||||||
const updatedOrganization =
|
error={error}
|
||||||
await updateOrganizationMutation.mutateAsync({
|
onSubmit={async (values) => {
|
||||||
organizationId: organization.id,
|
const updatedOrganization =
|
||||||
req: values,
|
await updateOrganizationMutation.mutateAsync({
|
||||||
});
|
organizationId: organization.id,
|
||||||
navigate(`/organizations/${updatedOrganization.name}/settings`);
|
req: values,
|
||||||
displaySuccess("Organization settings updated.");
|
});
|
||||||
}}
|
navigate(`/organizations/${updatedOrganization.name}/settings`);
|
||||||
onDeleteOrganization={async () => {
|
displaySuccess("Organization settings updated.");
|
||||||
try {
|
}}
|
||||||
await deleteOrganizationMutation.mutateAsync(organization.id);
|
onDeleteOrganization={async () => {
|
||||||
displaySuccess("Organization deleted");
|
try {
|
||||||
navigate("/organizations");
|
await deleteOrganizationMutation.mutateAsync(organization.id);
|
||||||
} catch (error) {
|
displaySuccess("Organization deleted");
|
||||||
displayError(getErrorMessage(error, "Failed to delete organization"));
|
navigate("/organizations");
|
||||||
}
|
} catch (error) {
|
||||||
}}
|
displayError(
|
||||||
/>
|
getErrorMessage(error, "Failed to delete organization"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { EmptyState } from "components/EmptyState/EmptyState";
|
|||||||
import { TabLink, Tabs, TabsList } from "components/Tabs/Tabs";
|
import { TabLink, Tabs, TabsList } from "components/Tabs/Tabs";
|
||||||
import { useSearchParamsKey } from "hooks/useSearchParamsKey";
|
import { useSearchParamsKey } from "hooks/useSearchParamsKey";
|
||||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||||
|
import { RequirePermission } from "modules/permissions/RequirePermission";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { pageTitle } from "utils/page";
|
import { pageTitle } from "utils/page";
|
||||||
@@ -16,26 +17,32 @@ const ProvisionersPage: FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!organization || !organizationPermissions?.viewProvisionerJobs) {
|
if (!organization || !organizationPermissions?.viewProvisionerJobs) {
|
||||||
|
return <EmptyState message="Organization not found" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const helmet = (
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{pageTitle(
|
||||||
|
"Provisioners",
|
||||||
|
organization.display_name || organization.name,
|
||||||
|
)}
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!organizationPermissions?.viewProvisioners) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
{helmet}
|
||||||
<title>{pageTitle("Provisioners")}</title>
|
<RequirePermission isFeatureVisible={false} />
|
||||||
</Helmet>
|
|
||||||
<EmptyState message="Organization not found" />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
{helmet}
|
||||||
<title>
|
|
||||||
{pageTitle(
|
|
||||||
"Provisioners",
|
|
||||||
organization.display_name || organization.name,
|
|
||||||
)}
|
|
||||||
</title>
|
|
||||||
</Helmet>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-12">
|
<div className="flex flex-col gap-12">
|
||||||
<header className="flex flex-row items-baseline justify-between">
|
<header className="flex flex-row items-baseline justify-between">
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ export const TemplatePageHeader: FC<TemplatePageHeaderProps> = ({
|
|||||||
onDeleteTemplate,
|
onDeleteTemplate,
|
||||||
}) => {
|
}) => {
|
||||||
const getLink = useLinks();
|
const getLink = useLinks();
|
||||||
const hasIcon = template.icon && template.icon !== "";
|
|
||||||
const templateLink = getLink(
|
const templateLink = getLink(
|
||||||
linkToTemplate(template.organization_name, template.name),
|
linkToTemplate(template.organization_name, template.name),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -110,25 +110,6 @@ interface ExternalAuthRowProps {
|
|||||||
onValidateExternalAuth: () => void;
|
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<ExternalAuthRowProps> = ({
|
const ExternalAuthRow: FC<ExternalAuthRowProps> = ({
|
||||||
app,
|
app,
|
||||||
unlinked,
|
unlinked,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ interface SidebarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Sidebar: FC<SidebarProps> = ({ user }) => {
|
export const Sidebar: FC<SidebarProps> = ({ user }) => {
|
||||||
const { entitlements, experiments } = useDashboard();
|
const { entitlements } = useDashboard();
|
||||||
const showSchedulePage =
|
const showSchedulePage =
|
||||||
entitlements.features.advanced_template_scheduling.enabled;
|
entitlements.features.advanced_template_scheduling.enabled;
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { useDashboard } from "modules/dashboard/useDashboard";
|
|||||||
import { type FC, useState } from "react";
|
import { type FC, useState } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
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 { pageTitle } from "utils/page";
|
||||||
import { generateRandomString } from "utils/random";
|
import { generateRandomString } from "utils/random";
|
||||||
import { ResetPasswordDialog } from "./ResetPasswordDialog";
|
import { ResetPasswordDialog } from "./ResetPasswordDialog";
|
||||||
@@ -39,7 +39,6 @@ type UserPageProps = {
|
|||||||
const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
|
const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
|
||||||
const searchParamsResult = useSearchParams();
|
const searchParamsResult = useSearchParams();
|
||||||
const { entitlements } = useDashboard();
|
const { entitlements } = useDashboard();
|
||||||
const [searchParams] = searchParamsResult;
|
const [searchParams] = searchParamsResult;
|
||||||
|
|||||||
Reference in New Issue
Block a user