chore: perform several small frontend permissions refactors (#16735)

This commit is contained in:
ケイラ
2025-03-07 10:33:09 -07:00
committed by GitHub
parent 54745b1d3f
commit 092c129de0
57 changed files with 158 additions and 174 deletions

View File

@ -167,8 +167,6 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) {
}) })
return return
} }
// TODO: It would be nice to enforce this at the schema level
// but unfortunately our org_members table does not have an ID.
_, err := database.ExpectOne(api.Database.OrganizationMembers(ctx, database.OrganizationMembersParams{ _, err := database.ExpectOne(api.Database.OrganizationMembers(ctx, database.OrganizationMembersParams{
OrganizationID: group.OrganizationID, OrganizationID: group.OrganizationID,
UserID: uuid.MustParse(id), UserID: uuid.MustParse(id),

View File

@ -20,10 +20,10 @@ export const defaultPassword = "SomeSecurePassword!";
// Credentials for users // Credentials for users
export const users = { export const users = {
admin: { owner: {
username: "admin", username: "owner",
password: defaultPassword, password: defaultPassword,
email: "admin@coder.com", email: "owner@coder.com",
}, },
templateAdmin: { templateAdmin: {
username: "template-admin", username: "template-admin",
@ -41,7 +41,7 @@ export const users = {
username: "auditor", username: "auditor",
password: defaultPassword, password: defaultPassword,
email: "auditor@coder.com", email: "auditor@coder.com",
roles: ["Template Admin", "Auditor"], roles: ["Auditor"],
}, },
member: { member: {
username: "member", username: "member",

View File

@ -67,7 +67,7 @@ export type LoginOptions = {
password: string; password: string;
}; };
export async function login(page: Page, options: LoginOptions = users.admin) { export async function login(page: Page, options: LoginOptions = users.owner) {
const ctx = page.context(); const ctx = page.context();
// biome-ignore lint/suspicious/noExplicitAny: reset the current user // biome-ignore lint/suspicious/noExplicitAny: reset the current user
(ctx as any)[Symbol.for("currentUser")] = undefined; (ctx as any)[Symbol.for("currentUser")] = undefined;

View File

@ -16,8 +16,8 @@ test("setup deployment", async ({ page }) => {
} }
// Setup first user // Setup first user
await page.getByLabel(Language.emailLabel).fill(users.admin.email); await page.getByLabel(Language.emailLabel).fill(users.owner.email);
await page.getByLabel(Language.passwordLabel).fill(users.admin.password); await page.getByLabel(Language.passwordLabel).fill(users.owner.password);
await page.getByTestId("create").click(); await page.getByTestId("create").click();
await expectUrl(page).toHavePathName("/workspaces"); await expectUrl(page).toHavePathName("/workspaces");
@ -25,7 +25,7 @@ test("setup deployment", async ({ page }) => {
for (const user of Object.values(users)) { for (const user of Object.values(users)) {
// Already created as first user // Already created as first user
if (user.username === "admin") { if (user.username === "owner") {
continue; continue;
} }

View File

@ -13,19 +13,17 @@ test.describe.configure({ mode: "parallel" });
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
beforeCoderTest(page); beforeCoderTest(page);
await login(page, users.auditor);
}); });
async function resetSearch(page: Page) { async function resetSearch(page: Page, username: string) {
const clearButton = page.getByLabel("Clear search"); const clearButton = page.getByLabel("Clear search");
if (await clearButton.isVisible()) { if (await clearButton.isVisible()) {
await clearButton.click(); await clearButton.click();
} }
// Filter by the auditor test user to prevent race conditions // Filter by the auditor test user to prevent race conditions
const user = currentUser(page);
await expect(page.getByText("All users")).toBeVisible(); await expect(page.getByText("All users")).toBeVisible();
await page.getByPlaceholder("Search...").fill(`username:${user.username}`); await page.getByPlaceholder("Search...").fill(`username:${username}`);
await expect(page.getByText("All users")).not.toBeVisible(); await expect(page.getByText("All users")).not.toBeVisible();
} }
@ -33,12 +31,14 @@ test("logins are logged", async ({ page }) => {
requiresLicense(); requiresLicense();
// Go to the audit history // Go to the audit history
await login(page, users.auditor);
await page.goto("/audit"); await page.goto("/audit");
const username = users.auditor.username;
const user = currentUser(page); const user = currentUser(page);
const loginMessage = `${user.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); await resetSearch(page, username);
await expect(page.getByText(loginMessage).first()).toBeVisible(); await expect(page.getByText(loginMessage).first()).toBeVisible();
}); });
@ -46,29 +46,30 @@ test("creating templates and workspaces is logged", async ({ page }) => {
requiresLicense(); requiresLicense();
// Do some stuff that should show up in the audit logs // Do some stuff that should show up in the audit logs
await login(page, users.templateAdmin);
const username = users.templateAdmin.username;
const templateName = await createTemplate(page); const templateName = await createTemplate(page);
const workspaceName = await createWorkspace(page, templateName); const workspaceName = await createWorkspace(page, templateName);
// Go to the audit history // Go to the audit history
await login(page, users.auditor);
await page.goto("/audit"); await page.goto("/audit");
const user = currentUser(page);
// Make sure those things we did all actually show up // Make sure those things we did all actually show up
await resetSearch(page); await resetSearch(page, username);
await expect( await expect(
page.getByText(`${user.username} created template ${templateName}`), page.getByText(`${username} created template ${templateName}`),
).toBeVisible(); ).toBeVisible();
await expect( await expect(
page.getByText(`${user.username} created workspace ${workspaceName}`), page.getByText(`${username} created workspace ${workspaceName}`),
).toBeVisible(); ).toBeVisible();
await expect( await expect(
page.getByText(`${user.username} started workspace ${workspaceName}`), page.getByText(`${username} started workspace ${workspaceName}`),
).toBeVisible(); ).toBeVisible();
// Make sure we can inspect the details of the log item // Make sure we can inspect the details of the log item
const createdWorkspace = page.locator(".MuiTableRow-root", { const createdWorkspace = page.locator(".MuiTableRow-root", {
hasText: `${user.username} created workspace ${workspaceName}`, hasText: `${username} created workspace ${workspaceName}`,
}); });
await createdWorkspace.getByLabel("open-dropdown").click(); await createdWorkspace.getByLabel("open-dropdown").click();
await expect( await expect(
@ -83,18 +84,19 @@ test("inspecting and filtering audit logs", async ({ page }) => {
requiresLicense(); requiresLicense();
// Do some stuff that should show up in the audit logs // Do some stuff that should show up in the audit logs
await login(page, users.templateAdmin);
const username = users.templateAdmin.username;
const templateName = await createTemplate(page); const templateName = await createTemplate(page);
const workspaceName = await createWorkspace(page, templateName); const workspaceName = await createWorkspace(page, templateName);
// Go to the audit history // Go to the audit history
await login(page, users.auditor);
await page.goto("/audit"); await page.goto("/audit");
const loginMessage = `${username} logged in`;
const user = currentUser(page); const startedWorkspaceMessage = `${username} started workspace ${workspaceName}`;
const loginMessage = `${user.username} logged in`;
const startedWorkspaceMessage = `${user.username} started workspace ${workspaceName}`;
// Filter by resource type // Filter by resource type
await resetSearch(page); await resetSearch(page, username);
await page.getByText("All resource types").click(); await page.getByText("All resource types").click();
const workspaceBuildsOption = page.getByText("Workspace Build"); const workspaceBuildsOption = page.getByText("Workspace Build");
await workspaceBuildsOption.scrollIntoViewIfNeeded({ timeout: 5000 }); await workspaceBuildsOption.scrollIntoViewIfNeeded({ timeout: 5000 });
@ -107,7 +109,7 @@ test("inspecting and filtering audit logs", async ({ page }) => {
await expect(page.getByText("All resource types")).toBeVisible(); await expect(page.getByText("All resource types")).toBeVisible();
// Filter by action type // Filter by action type
await resetSearch(page); await resetSearch(page, username);
await page.getByText("All actions").click(); await page.getByText("All actions").click();
await page.getByText("Login", { exact: true }).click(); await page.getByText("Login", { exact: true }).click();
// Logins should be visible // Logins should be visible

View File

@ -16,7 +16,7 @@ test("experiments", async ({ page }) => {
const availableExperiments = await API.getAvailableExperiments(); const availableExperiments = await API.getAvailableExperiments();
// Verify if the site lists the same experiments // Verify if the site lists the same experiments
await page.goto("/deployment/general", { waitUntil: "networkidle" }); await page.goto("/deployment/overview", { waitUntil: "domcontentloaded" });
const experimentsLocator = page.locator( const experimentsLocator = page.locator(
"div.options-table tr.option-experiments ul.option-array", "div.options-table tr.option-experiments ul.option-array",

View File

@ -82,8 +82,8 @@ test.describe("roles admin settings access", () => {
]); ]);
}); });
test("admin can see admin settings", async ({ page }) => { test("owner can see admin settings", async ({ page }) => {
await login(page, users.admin); await login(page, users.owner);
await page.goto("/", { waitUntil: "domcontentloaded" }); await page.goto("/", { waitUntil: "domcontentloaded" });
await hasAccessToAdminSettings(page, [ await hasAccessToAdminSettings(page, [

View File

@ -6,7 +6,7 @@ import type {
SerpentOption, SerpentOption,
User, User,
} from "api/typesGenerated"; } from "api/typesGenerated";
import type { Permissions } from "contexts/auth/permissions"; import type { Permissions } from "modules/permissions";
import type { QueryKey } from "react-query"; import type { QueryKey } from "react-query";
declare module "@storybook/react" { declare module "@storybook/react" {

View File

@ -9,7 +9,7 @@ import {
type OrganizationPermissionName, type OrganizationPermissionName,
type OrganizationPermissions, type OrganizationPermissions,
organizationPermissionChecks, organizationPermissionChecks,
} from "modules/management/organizationPermissions"; } from "modules/permissions/organizations";
import type { QueryClient } from "react-query"; import type { QueryClient } from "react-query";
import { meKey } from "./users"; import { meKey } from "./users";

View File

@ -10,6 +10,7 @@ import {
import type { UpdateUserProfileRequest, User } from "api/typesGenerated"; import type { UpdateUserProfileRequest, User } from "api/typesGenerated";
import { displaySuccess } from "components/GlobalSnackbar/utils"; import { displaySuccess } from "components/GlobalSnackbar/utils";
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
import { type Permissions, permissionChecks } from "modules/permissions";
import { import {
type FC, type FC,
type PropsWithChildren, type PropsWithChildren,
@ -18,7 +19,6 @@ import {
useContext, useContext,
} from "react"; } from "react";
import { useMutation, useQuery, useQueryClient } from "react-query"; import { useMutation, useQuery, useQueryClient } from "react-query";
import { type Permissions, permissionChecks } from "./permissions";
export type AuthContextValue = { export type AuthContextValue = {
isLoading: boolean; isLoading: boolean;

View File

@ -16,8 +16,8 @@ import { useUpdateCheck } from "./useUpdateCheck";
export const DashboardLayout: FC = () => { export const DashboardLayout: FC = () => {
const { permissions } = useAuthenticated(); const { permissions } = useAuthenticated();
const updateCheck = useUpdateCheck(permissions.viewUpdateCheck); const updateCheck = useUpdateCheck(permissions.viewDeploymentConfig);
const canViewDeployment = Boolean(permissions.viewDeploymentValues); const canViewDeployment = Boolean(permissions.viewDeploymentConfig);
return ( return (
<> <>

View File

@ -11,8 +11,8 @@ import type {
import { ErrorAlert } from "components/Alert/ErrorAlert"; import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { useAuthenticated } from "contexts/auth/RequireAuth"; import { useAuthenticated } from "contexts/auth/RequireAuth";
import { canViewAnyOrganization } from "contexts/auth/permissions";
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
import { canViewAnyOrganization } from "modules/permissions";
import { type FC, type PropsWithChildren, createContext } from "react"; import { type FC, type PropsWithChildren, createContext } from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { selectFeatureVisibility } from "./entitlements"; import { selectFeatureVisibility } from "./entitlements";

View File

@ -10,10 +10,10 @@ export const DeploymentBanner: FC = () => {
const deploymentStatsQuery = useQuery(deploymentStats()); const deploymentStatsQuery = useQuery(deploymentStats());
const healthQuery = useQuery({ const healthQuery = useQuery({
...health(), ...health(),
enabled: permissions.viewDeploymentValues, enabled: permissions.viewDeploymentConfig,
}); });
if (!permissions.viewDeploymentValues || !deploymentStatsQuery.data) { if (!permissions.viewDeploymentConfig || !deploymentStatsQuery.data) {
return null; return null;
} }

View File

@ -1,9 +1,9 @@
import { buildInfo } from "api/queries/buildInfo"; import { buildInfo } from "api/queries/buildInfo";
import { useProxy } from "contexts/ProxyContext"; import { useProxy } from "contexts/ProxyContext";
import { useAuthenticated } from "contexts/auth/RequireAuth"; import { useAuthenticated } from "contexts/auth/RequireAuth";
import { canViewDeploymentSettings } from "contexts/auth/permissions";
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
import { useDashboard } from "modules/dashboard/useDashboard"; import { useDashboard } from "modules/dashboard/useDashboard";
import { canViewDeploymentSettings } from "modules/permissions";
import type { FC } from "react"; import type { FC } from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { useFeatureVisibility } from "../useFeatureVisibility"; import { useFeatureVisibility } from "../useFeatureVisibility";

View File

@ -3,7 +3,7 @@ import { fn, userEvent, within } from "@storybook/test";
import { getAuthorizationKey } from "api/queries/authCheck"; import { getAuthorizationKey } from "api/queries/authCheck";
import { getPreferredProxy } from "contexts/ProxyContext"; import { getPreferredProxy } from "contexts/ProxyContext";
import { AuthProvider } from "contexts/auth/AuthProvider"; import { AuthProvider } from "contexts/auth/AuthProvider";
import { permissionChecks } from "contexts/auth/permissions"; import { permissionChecks } from "modules/permissions";
import { import {
MockAuthMethodsAll, MockAuthMethodsAll,
MockPermissions, MockPermissions,

View File

@ -6,26 +6,26 @@ import { type FC, createContext, useContext } from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
export const DeploymentSettingsContext = createContext< export const DeploymentConfigContext = createContext<
DeploymentSettingsValue | undefined DeploymentConfigValue | undefined
>(undefined); >(undefined);
type DeploymentSettingsValue = Readonly<{ type DeploymentConfigValue = Readonly<{
deploymentConfig: DeploymentConfig; deploymentConfig: DeploymentConfig;
}>; }>;
export const useDeploymentSettings = (): DeploymentSettingsValue => { export const useDeploymentConfig = (): DeploymentConfigValue => {
const context = useContext(DeploymentSettingsContext); const context = useContext(DeploymentConfigContext);
if (!context) { if (!context) {
throw new Error( throw new Error(
`${useDeploymentSettings.name} should be used inside of ${DeploymentSettingsProvider.name}`, `${useDeploymentConfig.name} should be used inside of ${DeploymentConfigProvider.name}`,
); );
} }
return context; return context;
}; };
const DeploymentSettingsProvider: FC = () => { const DeploymentConfigProvider: FC = () => {
const deploymentConfigQuery = useQuery(deploymentConfig()); const deploymentConfigQuery = useQuery(deploymentConfig());
if (deploymentConfigQuery.error) { if (deploymentConfigQuery.error) {
@ -37,12 +37,12 @@ const DeploymentSettingsProvider: FC = () => {
} }
return ( return (
<DeploymentSettingsContext.Provider <DeploymentConfigContext.Provider
value={{ deploymentConfig: deploymentConfigQuery.data }} value={{ deploymentConfig: deploymentConfigQuery.data }}
> >
<Outlet /> <Outlet />
</DeploymentSettingsContext.Provider> </DeploymentConfigContext.Provider>
); );
}; };
export default DeploymentSettingsProvider; export default DeploymentConfigProvider;

View File

@ -7,8 +7,8 @@ import {
} from "components/Breadcrumb/Breadcrumb"; } from "components/Breadcrumb/Breadcrumb";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { useAuthenticated } from "contexts/auth/RequireAuth"; import { useAuthenticated } from "contexts/auth/RequireAuth";
import { RequirePermission } from "contexts/auth/RequirePermission"; import { canViewDeploymentSettings } from "modules/permissions";
import { canViewDeploymentSettings } from "contexts/auth/permissions"; import { RequirePermission } from "modules/permissions/RequirePermission";
import { type FC, Suspense } from "react"; import { type FC, Suspense } from "react";
import { Navigate, Outlet, useLocation } from "react-router-dom"; import { Navigate, Outlet, useLocation } from "react-router-dom";
import { DeploymentSidebar } from "./DeploymentSidebar"; import { DeploymentSidebar } from "./DeploymentSidebar";
@ -21,8 +21,8 @@ const DeploymentSettingsLayout: FC = () => {
return ( return (
<Navigate <Navigate
to={ to={
permissions.viewDeploymentValues permissions.viewDeploymentConfig
? "/deployment/general" ? "/deployment/overview"
: "/deployment/users" : "/deployment/users"
} }
replace replace

View File

@ -47,8 +47,8 @@ export const NoDeploymentValues: Story = {
args: { args: {
permissions: { permissions: {
...MockPermissions, ...MockPermissions,
viewDeploymentValues: false, viewDeploymentConfig: false,
editDeploymentValues: false, editDeploymentConfig: false,
}, },
}, },
}; };

View File

@ -4,8 +4,8 @@ import {
SettingsSidebarNavItem as SidebarNavItem, SettingsSidebarNavItem as SidebarNavItem,
} from "components/Sidebar/Sidebar"; } from "components/Sidebar/Sidebar";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import type { Permissions } from "contexts/auth/permissions";
import { ArrowUpRight } from "lucide-react"; import { ArrowUpRight } from "lucide-react";
import type { Permissions } from "modules/permissions";
import type { FC } from "react"; import type { FC } from "react";
interface DeploymentSidebarViewProps { interface DeploymentSidebarViewProps {
@ -18,9 +18,6 @@ interface DeploymentSidebarViewProps {
/** /**
* Displays navigation for deployment settings. If active, highlight the main * Displays navigation for deployment settings. If active, highlight the main
* menu heading. * menu heading.
*
* Menu items are shown based on the permissions. If organizations can be
* viewed, groups are skipped since they will show under each org instead.
*/ */
export const DeploymentSidebarView: FC<DeploymentSidebarViewProps> = ({ export const DeploymentSidebarView: FC<DeploymentSidebarViewProps> = ({
permissions, permissions,
@ -30,32 +27,32 @@ export const DeploymentSidebarView: FC<DeploymentSidebarViewProps> = ({
return ( return (
<BaseSidebar> <BaseSidebar>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
{permissions.viewDeploymentValues && ( {permissions.viewDeploymentConfig && (
<SidebarNavItem href="/deployment/general">General</SidebarNavItem> <SidebarNavItem href="/deployment/overview">Overview</SidebarNavItem>
)} )}
{permissions.viewAllLicenses && ( {permissions.viewAllLicenses && (
<SidebarNavItem href="/deployment/licenses">Licenses</SidebarNavItem> <SidebarNavItem href="/deployment/licenses">Licenses</SidebarNavItem>
)} )}
{permissions.editDeploymentValues && ( {permissions.editDeploymentConfig && (
<SidebarNavItem href="/deployment/appearance"> <SidebarNavItem href="/deployment/appearance">
Appearance Appearance
</SidebarNavItem> </SidebarNavItem>
)} )}
{permissions.viewDeploymentValues && ( {permissions.viewDeploymentConfig && (
<SidebarNavItem href="/deployment/userauth"> <SidebarNavItem href="/deployment/userauth">
User Authentication User Authentication
</SidebarNavItem> </SidebarNavItem>
)} )}
{permissions.viewDeploymentValues && ( {permissions.viewDeploymentConfig && (
<SidebarNavItem href="/deployment/external-auth"> <SidebarNavItem href="/deployment/external-auth">
External Authentication External Authentication
</SidebarNavItem> </SidebarNavItem>
)} )}
{/* Not exposing this yet since token exchange is not finished yet. {/* Not exposing this yet since token exchange is not finished yet.
<SidebarNavItem href="oauth2-provider/ap"> <SidebarNavItem href="oauth2-provider/apps">
OAuth2 Applications OAuth2 Applications
</SidebarNavItem>*/} </SidebarNavItem>*/}
{permissions.viewDeploymentValues && ( {permissions.viewDeploymentConfig && (
<SidebarNavItem href="/deployment/network">Network</SidebarNavItem> <SidebarNavItem href="/deployment/network">Network</SidebarNavItem>
)} )}
{permissions.readWorkspaceProxies && ( {permissions.readWorkspaceProxies && (
@ -63,10 +60,10 @@ export const DeploymentSidebarView: FC<DeploymentSidebarViewProps> = ({
Workspace Proxies Workspace Proxies
</SidebarNavItem> </SidebarNavItem>
)} )}
{permissions.viewDeploymentValues && ( {permissions.viewDeploymentConfig && (
<SidebarNavItem href="/deployment/security">Security</SidebarNavItem> <SidebarNavItem href="/deployment/security">Security</SidebarNavItem>
)} )}
{permissions.viewDeploymentValues && ( {permissions.viewDeploymentConfig && (
<SidebarNavItem href="/deployment/observability"> <SidebarNavItem href="/deployment/observability">
Observability Observability
</SidebarNavItem> </SidebarNavItem>
@ -81,6 +78,11 @@ export const DeploymentSidebarView: FC<DeploymentSidebarViewProps> = ({
</Stack> </Stack>
</SidebarNavItem> </SidebarNavItem>
)} )}
{permissions.viewOrganizationIDPSyncSettings && (
<SidebarNavItem href="/deployment/idp-org-sync">
IdP Organization Sync
</SidebarNavItem>
)}
{permissions.viewNotificationTemplate && ( {permissions.viewNotificationTemplate && (
<SidebarNavItem href="/deployment/notifications"> <SidebarNavItem href="/deployment/notifications">
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
@ -89,11 +91,6 @@ export const DeploymentSidebarView: FC<DeploymentSidebarViewProps> = ({
</div> </div>
</SidebarNavItem> </SidebarNavItem>
)} )}
{permissions.viewOrganizationIDPSyncSettings && (
<SidebarNavItem href="/deployment/idp-org-sync">
IdP Organization Sync
</SidebarNavItem>
)}
{!hasPremiumLicense && ( {!hasPremiumLicense && (
<SidebarNavItem href="/deployment/premium">Premium</SidebarNavItem> <SidebarNavItem href="/deployment/premium">Premium</SidebarNavItem>
)} )}

View File

@ -11,14 +11,14 @@ import {
} from "components/Breadcrumb/Breadcrumb"; } from "components/Breadcrumb/Breadcrumb";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { useDashboard } from "modules/dashboard/useDashboard"; import { useDashboard } from "modules/dashboard/useDashboard";
import {
type OrganizationPermissions,
canViewOrganization,
} from "modules/permissions/organizations";
import NotFoundPage from "pages/404Page/404Page"; import NotFoundPage from "pages/404Page/404Page";
import { type FC, Suspense, createContext, useContext } from "react"; import { type FC, Suspense, createContext, useContext } from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { Outlet, useParams } from "react-router-dom"; import { Outlet, useParams } from "react-router-dom";
import {
type OrganizationPermissions,
canViewOrganization,
} from "./organizationPermissions";
export const OrganizationSettingsContext = createContext< export const OrganizationSettingsContext = createContext<
OrganizationSettingsValue | undefined OrganizationSettingsValue | undefined
@ -46,7 +46,7 @@ export const useOrganizationSettings = (): OrganizationSettingsValue => {
}; };
const OrganizationSettingsLayout: FC = () => { const OrganizationSettingsLayout: FC = () => {
const { organizations, showOrganizations } = useDashboard(); const { organizations } = useDashboard();
const { organization: orgName } = useParams() as { const { organization: orgName } = useParams() as {
organization?: string; organization?: string;
}; };

View File

@ -16,11 +16,11 @@ import {
PopoverTrigger, PopoverTrigger,
} from "components/Popover/Popover"; } from "components/Popover/Popover";
import { SettingsSidebarNavItem } from "components/Sidebar/Sidebar"; import { SettingsSidebarNavItem } from "components/Sidebar/Sidebar";
import type { Permissions } from "contexts/auth/permissions";
import { Check, ChevronDown, Plus } from "lucide-react"; import { Check, ChevronDown, Plus } from "lucide-react";
import type { Permissions } from "modules/permissions";
import type { OrganizationPermissions } from "modules/permissions/organizations";
import { type FC, useState } from "react"; import { type FC, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import type { OrganizationPermissions } from "./organizationPermissions";
interface OrganizationsSettingsNavigationProps { interface OrganizationsSettingsNavigationProps {
/** The organization selected from the dropdown */ /** The organization selected from the dropdown */

View File

@ -30,7 +30,7 @@ export const permissionChecks = {
resource_type: "template", resource_type: "template",
any_org: true, any_org: true,
}, },
action: "update", action: "create",
}, },
updateTemplates: { updateTemplates: {
object: { object: {
@ -44,30 +44,18 @@ export const permissionChecks = {
}, },
action: "delete", action: "delete",
}, },
viewDeploymentValues: { viewDeploymentConfig: {
object: { object: {
resource_type: "deployment_config", resource_type: "deployment_config",
}, },
action: "read", action: "read",
}, },
editDeploymentValues: { editDeploymentConfig: {
object: { object: {
resource_type: "deployment_config", resource_type: "deployment_config",
}, },
action: "update", action: "update",
}, },
viewUpdateCheck: {
object: {
resource_type: "deployment_config",
},
action: "read",
},
viewExternalAuthConfig: {
object: {
resource_type: "deployment_config",
},
action: "read",
},
viewDeploymentStats: { viewDeploymentStats: {
object: { object: {
resource_type: "deployment_stats", resource_type: "deployment_stats",
@ -178,7 +166,7 @@ export const canViewDeploymentSettings = (
): permissions is Permissions => { ): permissions is Permissions => {
return ( return (
permissions !== undefined && permissions !== undefined &&
(permissions.viewDeploymentValues || (permissions.viewDeploymentConfig ||
permissions.viewAllLicenses || permissions.viewAllLicenses ||
permissions.viewAllUsers || permissions.viewAllUsers ||
permissions.viewAnyGroup || permissions.viewAnyGroup ||

View File

@ -1,11 +1,11 @@
import { useDeploymentSettings } from "modules/management/DeploymentSettingsProvider"; import { useDeploymentConfig } from "modules/management/DeploymentConfigProvider";
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";
import { ExternalAuthSettingsPageView } from "./ExternalAuthSettingsPageView"; import { ExternalAuthSettingsPageView } from "./ExternalAuthSettingsPageView";
const ExternalAuthSettingsPage: FC = () => { const ExternalAuthSettingsPage: FC = () => {
const { deploymentConfig } = useDeploymentSettings(); const { deploymentConfig } = useDeploymentConfig();
return ( return (
<> <>

View File

@ -108,7 +108,7 @@ export const LicenseSeatConsumptionChart: FC<
</li> </li>
<li> <li>
<Link asChild> <Link asChild>
<RouterLink to="/deployment/general"> <RouterLink to="/deployment/overview">
Daily user activity Daily user activity
</RouterLink> </RouterLink>
</Link> </Link>

View File

@ -1,11 +1,11 @@
import { useDeploymentSettings } from "modules/management/DeploymentSettingsProvider"; import { useDeploymentConfig } from "modules/management/DeploymentConfigProvider";
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";
import { NetworkSettingsPageView } from "./NetworkSettingsPageView"; import { NetworkSettingsPageView } from "./NetworkSettingsPageView";
const NetworkSettingsPage: FC = () => { const NetworkSettingsPage: FC = () => {
const { deploymentConfig } = useDeploymentSettings(); const { deploymentConfig } = useDeploymentConfig();
return ( return (
<> <>

View File

@ -9,7 +9,7 @@ import { Loader } from "components/Loader/Loader";
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
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 { useDeploymentSettings } from "modules/management/DeploymentSettingsProvider"; import { useDeploymentConfig } from "modules/management/DeploymentConfigProvider";
import { castNotificationMethod } from "modules/notifications/utils"; import { castNotificationMethod } from "modules/notifications/utils";
import type { FC } from "react"; import type { FC } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
@ -22,7 +22,7 @@ import { NotificationEvents } from "./NotificationEvents";
import { Troubleshooting } from "./Troubleshooting"; import { Troubleshooting } from "./Troubleshooting";
export const NotificationsPage: FC = () => { export const NotificationsPage: FC = () => {
const { deploymentConfig } = useDeploymentSettings(); const { deploymentConfig } = useDeploymentConfig();
const [templatesByGroup, dispatchMethods] = useQueries({ const [templatesByGroup, dispatchMethods] = useQueries({
queries: [ queries: [
{ {

View File

@ -194,7 +194,7 @@ export const baseMeta = {
}, },
], ],
user: MockUser, user: MockUser,
permissions: { viewDeploymentValues: true }, permissions: { viewDeploymentConfig: true },
deploymentOptions: mockNotificationsDeploymentOptions, deploymentOptions: mockNotificationsDeploymentOptions,
deploymentValues: { deploymentValues: {
notifications: { notifications: {

View File

@ -1,13 +1,13 @@
import { useDashboard } from "modules/dashboard/useDashboard"; import { useDashboard } from "modules/dashboard/useDashboard";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import { useDeploymentSettings } from "modules/management/DeploymentSettingsProvider"; import { useDeploymentConfig } from "modules/management/DeploymentConfigProvider";
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";
import { ObservabilitySettingsPageView } from "./ObservabilitySettingsPageView"; import { ObservabilitySettingsPageView } from "./ObservabilitySettingsPageView";
const ObservabilitySettingsPage: FC = () => { const ObservabilitySettingsPage: FC = () => {
const { deploymentConfig } = useDeploymentSettings(); const { deploymentConfig } = useDeploymentConfig();
const { entitlements } = useDashboard(); const { entitlements } = useDashboard();
const { multiple_organizations: hasPremiumLicense } = useFeatureVisibility(); const { multiple_organizations: hasPremiumLicense } = useFeatureVisibility();

View File

@ -1,15 +1,15 @@
import { deploymentDAUs } from "api/queries/deployment"; import { deploymentDAUs } from "api/queries/deployment";
import { availableExperiments, experiments } from "api/queries/experiments"; import { availableExperiments, experiments } from "api/queries/experiments";
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
import { useDeploymentSettings } from "modules/management/DeploymentSettingsProvider"; import { useDeploymentConfig } from "modules/management/DeploymentConfigProvider";
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";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import { GeneralSettingsPageView } from "./GeneralSettingsPageView"; import { OverviewPageView } from "./OverviewPageView";
const GeneralSettingsPage: FC = () => { const OverviewPage: FC = () => {
const { deploymentConfig } = useDeploymentSettings(); const { deploymentConfig } = useDeploymentConfig();
const safeExperimentsQuery = useQuery(availableExperiments()); const safeExperimentsQuery = useQuery(availableExperiments());
const { metadata } = useEmbeddedMetadata(); const { metadata } = useEmbeddedMetadata();
@ -26,9 +26,9 @@ const GeneralSettingsPage: FC = () => {
return ( return (
<> <>
<Helmet> <Helmet>
<title>{pageTitle("General Settings")}</title> <title>{pageTitle("Overview", "Deployment")}</title>
</Helmet> </Helmet>
<GeneralSettingsPageView <OverviewPageView
deploymentOptions={deploymentConfig.options} deploymentOptions={deploymentConfig.options}
dailyActiveUsers={dailyActiveUsers} dailyActiveUsers={dailyActiveUsers}
invalidExperiments={invalidExperiments} invalidExperiments={invalidExperiments}
@ -38,4 +38,4 @@ const GeneralSettingsPage: FC = () => {
); );
}; };
export default GeneralSettingsPage; export default OverviewPage;

View File

@ -1,10 +1,10 @@
import type { Meta, StoryObj } from "@storybook/react"; import type { Meta, StoryObj } from "@storybook/react";
import { MockDeploymentDAUResponse } from "testHelpers/entities"; import { MockDeploymentDAUResponse } from "testHelpers/entities";
import { GeneralSettingsPageView } from "./GeneralSettingsPageView"; import { OverviewPageView } from "./OverviewPageView";
const meta: Meta<typeof GeneralSettingsPageView> = { const meta: Meta<typeof OverviewPageView> = {
title: "pages/DeploymentSettingsPage/GeneralSettingsPageView", title: "pages/DeploymentSettingsPage/OverviewPageView",
component: GeneralSettingsPageView, component: OverviewPageView,
args: { args: {
deploymentOptions: [ deploymentOptions: [
{ {
@ -42,7 +42,7 @@ const meta: Meta<typeof GeneralSettingsPageView> = {
}; };
export default meta; export default meta;
type Story = StoryObj<typeof GeneralSettingsPageView>; type Story = StoryObj<typeof OverviewPageView>;
export const Page: Story = {}; export const Page: Story = {};

View File

@ -14,14 +14,14 @@ import { Alert } from "../../../components/Alert/Alert";
import OptionsTable from "../OptionsTable"; import OptionsTable from "../OptionsTable";
import { UserEngagementChart } from "./UserEngagementChart"; import { UserEngagementChart } from "./UserEngagementChart";
export type GeneralSettingsPageViewProps = { export type OverviewPageViewProps = {
deploymentOptions: SerpentOption[]; deploymentOptions: SerpentOption[];
dailyActiveUsers: DAUsResponse | undefined; dailyActiveUsers: DAUsResponse | undefined;
readonly invalidExperiments: Experiments | string[]; readonly invalidExperiments: Experiments | string[];
readonly safeExperiments: Experiments | string[]; readonly safeExperiments: Experiments | string[];
}; };
export const GeneralSettingsPageView: FC<GeneralSettingsPageViewProps> = ({ export const OverviewPageView: FC<OverviewPageViewProps> = ({
deploymentOptions, deploymentOptions,
dailyActiveUsers, dailyActiveUsers,
safeExperiments, safeExperiments,

View File

@ -1,12 +1,12 @@
import { useDashboard } from "modules/dashboard/useDashboard"; import { useDashboard } from "modules/dashboard/useDashboard";
import { useDeploymentSettings } from "modules/management/DeploymentSettingsProvider"; import { useDeploymentConfig } from "modules/management/DeploymentConfigProvider";
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";
import { SecuritySettingsPageView } from "./SecuritySettingsPageView"; import { SecuritySettingsPageView } from "./SecuritySettingsPageView";
const SecuritySettingsPage: FC = () => { const SecuritySettingsPage: FC = () => {
const { deploymentConfig } = useDeploymentSettings(); const { deploymentConfig } = useDeploymentConfig();
const { entitlements } = useDashboard(); const { entitlements } = useDashboard();
return ( return (

View File

@ -1,11 +1,11 @@
import { useDeploymentSettings } from "modules/management/DeploymentSettingsProvider"; import { useDeploymentConfig } from "modules/management/DeploymentConfigProvider";
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";
import { UserAuthSettingsPageView } from "./UserAuthSettingsPageView"; import { UserAuthSettingsPageView } from "./UserAuthSettingsPageView";
const UserAuthSettingsPage: FC = () => { const UserAuthSettingsPage: FC = () => {
const { deploymentConfig } = useDeploymentSettings(); const { deploymentConfig } = useDeploymentConfig();
return ( return (
<> <>

View File

@ -104,7 +104,7 @@ const ExternalAuthPage: FC = () => {
authenticated: false, authenticated: false,
}); });
}} }}
viewExternalAuthConfig={permissions.viewExternalAuthConfig} viewExternalAuthConfig={permissions.viewDeploymentConfig}
deviceExchangeError={deviceExchangeError} deviceExchangeError={deviceExchangeError}
externalAuthDevice={externalAuthDeviceQuery.data} externalAuthDevice={externalAuthDeviceQuery.data}
/> />

View File

@ -1,8 +1,8 @@
import { createOrganization } from "api/queries/organizations"; import { createOrganization } from "api/queries/organizations";
import { displaySuccess } from "components/GlobalSnackbar/utils"; import { displaySuccess } from "components/GlobalSnackbar/utils";
import { useAuthenticated } from "contexts/auth/RequireAuth"; import { useAuthenticated } from "contexts/auth/RequireAuth";
import { RequirePermission } from "contexts/auth/RequirePermission";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import { RequirePermission } from "modules/permissions/RequirePermission";
import type { FC } from "react"; import type { FC } from "react";
import { useMutation, useQueryClient } from "react-query"; import { useMutation, useQueryClient } from "react-query";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";

View File

@ -8,8 +8,8 @@ import type { CustomRoleRequest } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert"; import { ErrorAlert } from "components/Alert/ErrorAlert";
import { displayError } from "components/GlobalSnackbar/utils"; import { displayError } from "components/GlobalSnackbar/utils";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { RequirePermission } from "contexts/auth/RequirePermission";
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 { useMutation, useQuery, useQueryClient } from "react-query"; import { useMutation, useQuery, useQueryClient } from "react-query";

View File

@ -6,9 +6,9 @@ import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
import { Loader } from "components/Loader/Loader"; 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 { RequirePermission } from "contexts/auth/RequirePermission";
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, useQuery, useQueryClient } from "react-query"; import { useMutation, useQuery, useQueryClient } from "react-query";

View File

@ -1,6 +1,6 @@
import { EmptyState } from "components/EmptyState/EmptyState"; import { EmptyState } from "components/EmptyState/EmptyState";
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
import { canEditOrganization } from "modules/management/organizationPermissions"; import { canEditOrganization } from "modules/permissions/organizations";
import type { FC } from "react"; import type { FC } from "react";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
@ -10,19 +10,25 @@ const OrganizationRedirect: FC = () => {
organizationPermissionsByOrganizationId: organizationPermissions, organizationPermissionsByOrganizationId: organizationPermissions,
} = useOrganizationSettings(); } = useOrganizationSettings();
const sortedOrganizations = [...organizations].sort(
(a, b) => (b.is_default ? 1 : 0) - (a.is_default ? 1 : 0),
);
// Redirect /organizations => /organizations/some-organization-name // Redirect /organizations => /organizations/some-organization-name
// If they can edit the default org, we should redirect to the default. // If they can edit the default org, we should redirect to the default.
// If they cannot edit the default, we should redirect to the first org that // If they cannot edit the default, we should redirect to the first org that
// they can edit. // they can edit.
const editableOrg = [...organizations] const editableOrg = sortedOrganizations.find((org) =>
.sort((a, b) => (b.is_default ? 1 : 0) - (a.is_default ? 1 : 0)) canEditOrganization(organizationPermissions[org.id]),
.find((org) => canEditOrganization(organizationPermissions[org.id])); );
if (editableOrg) { if (editableOrg) {
return <Navigate to={`/organizations/${editableOrg.name}`} replace />; return <Navigate to={`/organizations/${editableOrg.name}`} replace />;
} }
// If they cannot edit any org, just redirect to an org they can read. // If they cannot edit any org, just redirect to an org they can read.
if (organizations.length > 0) { if (sortedOrganizations.length > 0) {
return <Navigate to={`/organizations/${organizations[0].name}`} replace />; return (
<Navigate to={`/organizations/${sortedOrganizations[0].name}`} replace />
);
} }
return <EmptyState message="No organizations found" />; return <EmptyState message="No organizations found" />;
}; };

View File

@ -4,7 +4,7 @@ import { workspaceByOwnerAndNameKey } from "api/queries/workspaces";
import type { Workspace, WorkspaceAgentLifecycle } from "api/typesGenerated"; import type { Workspace, WorkspaceAgentLifecycle } from "api/typesGenerated";
import { AuthProvider } from "contexts/auth/AuthProvider"; import { AuthProvider } from "contexts/auth/AuthProvider";
import { RequireAuth } from "contexts/auth/RequireAuth"; import { RequireAuth } from "contexts/auth/RequireAuth";
import { permissionChecks } from "contexts/auth/permissions"; import { permissionChecks } from "modules/permissions";
import { import {
reactRouterOutlet, reactRouterOutlet,
reactRouterParameters, reactRouterParameters,

View File

@ -40,7 +40,7 @@ const meta = {
}, },
], ],
user: MockUser, user: MockUser,
permissions: { viewDeploymentValues: true }, permissions: { viewDeploymentConfig: true },
}, },
decorators: [withGlobalSnackbar, withAuthProvider, withDashboardProvider], decorators: [withGlobalSnackbar, withAuthProvider, withDashboardProvider],
} satisfies Meta<typeof NotificationsPage>; } satisfies Meta<typeof NotificationsPage>;
@ -74,7 +74,7 @@ export const ToggleNotification: Story = {
export const NonAdmin: Story = { export const NonAdmin: Story = {
parameters: { parameters: {
permissions: { viewDeploymentValues: false }, permissions: { viewDeploymentConfig: false },
}, },
}; };

View File

@ -48,7 +48,7 @@ export const NotificationsPage: FC = () => {
...systemNotificationTemplates(), ...systemNotificationTemplates(),
select: (data: NotificationTemplate[]) => { select: (data: NotificationTemplate[]) => {
const groups = selectTemplatesByGroup(data); const groups = selectTemplatesByGroup(data);
return permissions.viewDeploymentValues return permissions.viewDeploymentConfig
? groups ? groups
: { : {
// Members only have access to the "Workspace Notifications" group // Members only have access to the "Workspace Notifications" group

View File

@ -63,7 +63,7 @@ const parameters = {
permissions: { permissions: {
createUser: true, createUser: true,
updateUsers: true, updateUsers: true,
viewDeploymentValues: true, viewDeploymentConfig: true,
}, },
}; };

View File

@ -51,12 +51,12 @@ const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
const { const {
createUser: canCreateUser, createUser: canCreateUser,
updateUsers: canEditUsers, updateUsers: canEditUsers,
viewDeploymentValues, viewDeploymentConfig,
} = permissions; } = permissions;
const rolesQuery = useQuery(roles()); const rolesQuery = useQuery(roles());
const { data: deploymentValues } = useQuery({ const { data: deploymentValues } = useQuery({
...deploymentConfig(), ...deploymentConfig(),
enabled: viewDeploymentValues, enabled: viewDeploymentConfig,
}); });
const usersQuery = usePaginatedQuery(paginatedUsers(searchParamsResult[0])); const usersQuery = usePaginatedQuery(paginatedUsers(searchParamsResult[0]));
@ -94,7 +94,7 @@ const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
// Indicates if oidc roles are synced from the oidc idp. // Indicates if oidc roles are synced from the oidc idp.
// Assign 'false' if unknown. // Assign 'false' if unknown.
const oidcRoleSyncEnabled = const oidcRoleSyncEnabled =
viewDeploymentValues && viewDeploymentConfig &&
deploymentValues?.config.oidc?.user_role_field !== ""; deploymentValues?.config.oidc?.user_role_field !== "";
const isLoading = const isLoading =

View File

@ -11,7 +11,7 @@ const permissions: WorkspacePermissions = {
readWorkspace: true, readWorkspace: true,
updateWorkspace: true, updateWorkspace: true,
updateTemplate: true, updateTemplate: true,
viewDeploymentValues: true, viewDeploymentConfig: true,
}; };
const meta: Meta<typeof Workspace> = { const meta: Meta<typeof Workspace> = {

View File

@ -15,7 +15,7 @@ const defaultPermissions = {
readWorkspace: true, readWorkspace: true,
updateTemplate: true, updateTemplate: true,
updateWorkspace: true, updateWorkspace: true,
viewDeploymentValues: true, viewDeploymentConfig: true,
}; };
const meta: Meta<typeof WorkspaceNotifications> = { const meta: Meta<typeof WorkspaceNotifications> = {

View File

@ -66,7 +66,7 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
// Debug mode // Debug mode
const { data: deploymentValues } = useQuery({ const { data: deploymentValues } = useQuery({
...deploymentConfig(), ...deploymentConfig(),
enabled: permissions.viewDeploymentValues, enabled: permissions.viewDeploymentConfig,
}); });
// Build logs // Build logs

View File

@ -25,7 +25,7 @@ export const workspaceChecks = (workspace: Workspace, template: Template) =>
}, },
action: "update", action: "update",
}, },
viewDeploymentValues: { viewDeploymentConfig: {
object: { object: {
resource_type: "deployment_config", resource_type: "deployment_config",
}, },

View File

@ -156,7 +156,7 @@ const useWorkspacesFilter = ({
}); });
const { permissions } = useAuthenticated(); const { permissions } = useAuthenticated();
const canFilterByUser = permissions.viewDeploymentValues; const canFilterByUser = permissions.viewDeploymentConfig;
const userMenu = useUserFilterMenu({ const userMenu = useUserFilterMenu({
value: filter.values.owner, value: filter.values.owner,
onChange: (option) => onChange: (option) =>

View File

@ -31,8 +31,8 @@ const NotFoundPage = lazy(() => import("./pages/404Page/404Page"));
const DeploymentSettingsLayout = lazy( const DeploymentSettingsLayout = lazy(
() => import("./modules/management/DeploymentSettingsLayout"), () => import("./modules/management/DeploymentSettingsLayout"),
); );
const DeploymentSettingsProvider = lazy( const DeploymentConfigProvider = lazy(
() => import("./modules/management/DeploymentSettingsProvider"), () => import("./modules/management/DeploymentConfigProvider"),
); );
const OrganizationSidebarLayout = lazy( const OrganizationSidebarLayout = lazy(
() => import("./modules/management/OrganizationSidebarLayout"), () => import("./modules/management/OrganizationSidebarLayout"),
@ -98,11 +98,8 @@ const TemplateSummaryPage = lazy(
const CreateWorkspacePage = lazy( const CreateWorkspacePage = lazy(
() => import("./pages/CreateWorkspacePage/CreateWorkspacePage"), () => import("./pages/CreateWorkspacePage/CreateWorkspacePage"),
); );
const GeneralSettingsPage = lazy( const OverviewPage = lazy(
() => () => import("./pages/DeploymentSettingsPage/OverviewPage/OverviewPage"),
import(
"./pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage"
),
); );
const SecuritySettingsPage = lazy( const SecuritySettingsPage = lazy(
() => () =>
@ -435,8 +432,8 @@ export const router = createBrowserRouter(
</Route> </Route>
<Route path="/deployment" element={<DeploymentSettingsLayout />}> <Route path="/deployment" element={<DeploymentSettingsLayout />}>
<Route element={<DeploymentSettingsProvider />}> <Route element={<DeploymentConfigProvider />}>
<Route path="general" element={<GeneralSettingsPage />} /> <Route path="overview" element={<OverviewPage />} />
<Route path="security" element={<SecuritySettingsPage />} /> <Route path="security" element={<SecuritySettingsPage />} />
<Route <Route
path="observability" path="observability"

View File

@ -5,10 +5,10 @@ import {
} from "api/api"; } from "api/api";
import type { FieldError } from "api/errors"; import type { FieldError } from "api/errors";
import type * as TypesGen from "api/typesGenerated"; import type * as TypesGen from "api/typesGenerated";
import type { Permissions } from "contexts/auth/permissions";
import type { ProxyLatencyReport } from "contexts/useProxyLatency"; import type { ProxyLatencyReport } from "contexts/useProxyLatency";
import range from "lodash/range"; import range from "lodash/range";
import type { OrganizationPermissions } from "modules/management/organizationPermissions"; import type { Permissions } from "modules/permissions";
import type { OrganizationPermissions } from "modules/permissions/organizations";
import type { FileTree } from "utils/filetree"; import type { FileTree } from "utils/filetree";
import type { TemplateVersionFiles } from "utils/templateVersion"; import type { TemplateVersionFiles } from "utils/templateVersion";
@ -2844,11 +2844,9 @@ export const MockPermissions: Permissions = {
viewAllUsers: true, viewAllUsers: true,
updateUsers: true, updateUsers: true,
viewAnyAuditLog: true, viewAnyAuditLog: true,
viewDeploymentValues: true, viewDeploymentConfig: true,
editDeploymentValues: true, editDeploymentConfig: true,
viewUpdateCheck: true,
viewDeploymentStats: true, viewDeploymentStats: true,
viewExternalAuthConfig: true,
readWorkspaceProxies: true, readWorkspaceProxies: true,
editWorkspaceProxies: true, editWorkspaceProxies: true,
createOrganization: true, createOrganization: true,
@ -2873,11 +2871,9 @@ export const MockNoPermissions: Permissions = {
viewAllUsers: false, viewAllUsers: false,
updateUsers: false, updateUsers: false,
viewAnyAuditLog: false, viewAnyAuditLog: false,
viewDeploymentValues: false, viewDeploymentConfig: false,
editDeploymentValues: false, editDeploymentConfig: false,
viewUpdateCheck: false,
viewDeploymentStats: false, viewDeploymentStats: false,
viewExternalAuthConfig: false,
readWorkspaceProxies: false, readWorkspaceProxies: false,
editWorkspaceProxies: false, editWorkspaceProxies: false,
createOrganization: false, createOrganization: false,

View File

@ -1,7 +1,7 @@
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import type { CreateWorkspaceBuildRequest } from "api/typesGenerated"; import type { CreateWorkspaceBuildRequest } from "api/typesGenerated";
import { permissionChecks } from "contexts/auth/permissions"; import { permissionChecks } from "modules/permissions";
import { http, HttpResponse } from "msw"; import { http, HttpResponse } from "msw";
import * as M from "./entities"; import * as M from "./entities";
import { MockGroup, MockWorkspaceQuota } from "./entities"; import { MockGroup, MockWorkspaceQuota } from "./entities";

View File

@ -6,10 +6,10 @@ import { hasFirstUserKey, meKey } from "api/queries/users";
import type { Entitlements } from "api/typesGenerated"; import type { Entitlements } from "api/typesGenerated";
import { GlobalSnackbar } from "components/GlobalSnackbar/GlobalSnackbar"; import { GlobalSnackbar } from "components/GlobalSnackbar/GlobalSnackbar";
import { AuthProvider } from "contexts/auth/AuthProvider"; import { AuthProvider } from "contexts/auth/AuthProvider";
import { permissionChecks } from "contexts/auth/permissions";
import { DashboardContext } from "modules/dashboard/DashboardProvider"; import { DashboardContext } from "modules/dashboard/DashboardProvider";
import { DeploymentSettingsContext } from "modules/management/DeploymentSettingsProvider"; import { DeploymentConfigContext } from "modules/management/DeploymentConfigProvider";
import { OrganizationSettingsContext } from "modules/management/OrganizationSettingsLayout"; import { OrganizationSettingsContext } from "modules/management/OrganizationSettingsLayout";
import { permissionChecks } from "modules/permissions";
import type { FC } from "react"; import type { FC } from "react";
import { useQueryClient } from "react-query"; import { useQueryClient } from "react-query";
import { import {
@ -168,11 +168,11 @@ export const withOrganizationSettingsProvider = (Story: FC) => {
organizationPermissions: MockOrganizationPermissions, organizationPermissions: MockOrganizationPermissions,
}} }}
> >
<DeploymentSettingsContext.Provider <DeploymentConfigContext.Provider
value={{ deploymentConfig: MockDeploymentConfig }} value={{ deploymentConfig: MockDeploymentConfig }}
> >
<Story /> <Story />
</DeploymentSettingsContext.Provider> </DeploymentConfigContext.Provider>
</OrganizationSettingsContext.Provider> </OrganizationSettingsContext.Provider>
); );
}; };