mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: set permissions for experimental Createworkspace page (#17254)
This commit is contained in:
@@ -3,7 +3,7 @@ export const workspacePermissionChecks = (
|
||||
userId: string,
|
||||
) =>
|
||||
({
|
||||
createWorkspace: {
|
||||
createWorkspaceForUserID: {
|
||||
object: {
|
||||
resource_type: "workspace",
|
||||
organization_id: organizationId,
|
||||
|
||||
@@ -26,7 +26,10 @@ import { pageTitle } from "utils/page";
|
||||
import type { AutofillBuildParameter } from "utils/richParameters";
|
||||
import { paramsUsedToCreateWorkspace } from "utils/workspace";
|
||||
import { CreateWorkspacePageView } from "./CreateWorkspacePageView";
|
||||
import { type CreateWSPermissions, createWorkspaceChecks } from "./permissions";
|
||||
import {
|
||||
type CreateWorkspacePermissions,
|
||||
createWorkspaceChecks,
|
||||
} from "./permissions";
|
||||
|
||||
export const createWorkspaceModes = ["form", "auto", "duplicate"] as const;
|
||||
export type CreateWorkspaceMode = (typeof createWorkspaceModes)[number];
|
||||
@@ -206,7 +209,7 @@ const CreateWorkspacePage: FC = () => {
|
||||
externalAuthPollingState={externalAuthPollingState}
|
||||
startPollingExternalAuth={startPollingExternalAuth}
|
||||
hasAllRequiredExternalAuth={hasAllRequiredExternalAuth}
|
||||
permissions={permissionsQuery.data as CreateWSPermissions}
|
||||
permissions={permissionsQuery.data as CreateWorkspacePermissions}
|
||||
parameters={realizedParameters as TemplateVersionParameter[]}
|
||||
presets={templateVersionPresetsQuery.data ?? []}
|
||||
creatingWorkspace={createWorkspaceMutation.isLoading}
|
||||
|
||||
@@ -17,10 +17,6 @@ import { Loader } from "components/Loader/Loader";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { useEffectEvent } from "hooks/hookPolyfills";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import {
|
||||
type WorkspacePermissions,
|
||||
workspacePermissionChecks,
|
||||
} from "modules/permissions/workspaces";
|
||||
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
|
||||
import { type FC, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
@@ -32,6 +28,10 @@ import { paramsUsedToCreateWorkspace } from "utils/workspace";
|
||||
import { CreateWorkspacePageViewExperimental } from "./CreateWorkspacePageViewExperimental";
|
||||
export const createWorkspaceModes = ["form", "auto", "duplicate"] as const;
|
||||
export type CreateWorkspaceMode = (typeof createWorkspaceModes)[number];
|
||||
import {
|
||||
type CreateWorkspacePermissions,
|
||||
createWorkspaceChecks,
|
||||
} from "./permissions";
|
||||
|
||||
export type ExternalAuthPollingState = "idle" | "polling" | "abandoned";
|
||||
|
||||
@@ -66,10 +66,7 @@ const CreateWorkspacePageExperimental: FC = () => {
|
||||
const permissionsQuery = useQuery(
|
||||
templateQuery.data
|
||||
? checkAuthorization({
|
||||
checks: workspacePermissionChecks(
|
||||
templateQuery.data.organization_id,
|
||||
me.id,
|
||||
),
|
||||
checks: createWorkspaceChecks(templateQuery.data.organization_id),
|
||||
})
|
||||
: { enabled: false },
|
||||
);
|
||||
@@ -211,7 +208,7 @@ const CreateWorkspacePageExperimental: FC = () => {
|
||||
externalAuthPollingState={externalAuthPollingState}
|
||||
startPollingExternalAuth={startPollingExternalAuth}
|
||||
hasAllRequiredExternalAuth={hasAllRequiredExternalAuth}
|
||||
permissions={permissionsQuery.data as WorkspacePermissions}
|
||||
permissions={permissionsQuery.data as CreateWorkspacePermissions}
|
||||
parameters={realizedParameters as TemplateVersionParameter[]}
|
||||
presets={templateVersionPresetsQuery.data ?? []}
|
||||
creatingWorkspace={createWorkspaceMutation.isLoading}
|
||||
|
||||
@@ -27,7 +27,7 @@ const meta: Meta<typeof CreateWorkspacePageView> = {
|
||||
hasAllRequiredExternalAuth: true,
|
||||
mode: "form",
|
||||
permissions: {
|
||||
createWorkspaceForUser: true,
|
||||
createWorkspaceForAny: true,
|
||||
},
|
||||
onCancel: action("onCancel"),
|
||||
},
|
||||
|
||||
@@ -46,7 +46,7 @@ import type {
|
||||
ExternalAuthPollingState,
|
||||
} from "./CreateWorkspacePage";
|
||||
import { ExternalAuthButton } from "./ExternalAuthButton";
|
||||
import type { CreateWSPermissions } from "./permissions";
|
||||
import type { CreateWorkspacePermissions } from "./permissions";
|
||||
export const Language = {
|
||||
duplicationWarning:
|
||||
"Duplicating a workspace only copies its parameters. No state from the old workspace is copied over.",
|
||||
@@ -68,7 +68,7 @@ export interface CreateWorkspacePageViewProps {
|
||||
parameters: TypesGen.TemplateVersionParameter[];
|
||||
autofillParameters: AutofillBuildParameter[];
|
||||
presets: TypesGen.Preset[];
|
||||
permissions: CreateWSPermissions;
|
||||
permissions: CreateWorkspacePermissions;
|
||||
creatingWorkspace: boolean;
|
||||
onCancel: () => void;
|
||||
onSubmit: (
|
||||
@@ -255,7 +255,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
<FormSection
|
||||
title="General"
|
||||
description={
|
||||
permissions.createWorkspaceForUser
|
||||
permissions.createWorkspaceForAny
|
||||
? "The name of the workspace and its owner. Only admins can create workspaces for other users."
|
||||
: "The name of your new workspace."
|
||||
}
|
||||
@@ -300,7 +300,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
</FormHelperText>
|
||||
</div>
|
||||
|
||||
{permissions.createWorkspaceForUser && (
|
||||
{permissions.createWorkspaceForAny && (
|
||||
<UserAutocomplete
|
||||
value={owner}
|
||||
onChange={(user) => {
|
||||
|
||||
@@ -15,7 +15,6 @@ import { Stack } from "components/Stack/Stack";
|
||||
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
|
||||
import { type FormikContextType, useFormik } from "formik";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import type { WorkspacePermissions } from "modules/permissions/workspaces";
|
||||
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
|
||||
import {
|
||||
type FC,
|
||||
@@ -37,7 +36,7 @@ import type {
|
||||
ExternalAuthPollingState,
|
||||
} from "./CreateWorkspacePage";
|
||||
import { ExternalAuthButton } from "./ExternalAuthButton";
|
||||
|
||||
import type { CreateWorkspacePermissions } from "./permissions";
|
||||
export const Language = {
|
||||
duplicationWarning:
|
||||
"Duplicating a workspace only copies its parameters. No state from the old workspace is copied over.",
|
||||
@@ -59,7 +58,7 @@ export interface CreateWorkspacePageViewExperimentalProps {
|
||||
parameters: TypesGen.TemplateVersionParameter[];
|
||||
autofillParameters: AutofillBuildParameter[];
|
||||
presets: TypesGen.Preset[];
|
||||
permissions: WorkspacePermissions;
|
||||
permissions: CreateWorkspacePermissions;
|
||||
creatingWorkspace: boolean;
|
||||
onCancel: () => void;
|
||||
onSubmit: (
|
||||
@@ -253,7 +252,7 @@ export const CreateWorkspacePageViewExperimental: FC<
|
||||
<hgroup>
|
||||
<h2 className="text-xl font-semibold m-0">General</h2>
|
||||
<p className="text-sm text-content-secondary mt-0">
|
||||
{permissions.createWorkspace
|
||||
{permissions.createWorkspaceForAny
|
||||
? "Only admins can create workspaces for other users."
|
||||
: "The name of your new workspace."}
|
||||
</p>
|
||||
@@ -300,7 +299,7 @@ export const CreateWorkspacePageViewExperimental: FC<
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{permissions.createWorkspace && (
|
||||
{permissions.createWorkspaceForAny && (
|
||||
<div className="flex flex-col gap-2 flex-1">
|
||||
<Label className="text-sm" htmlFor={`${id}-workspace-name`}>
|
||||
Owner
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const createWorkspaceChecks = (organizationId: string) =>
|
||||
({
|
||||
createWorkspaceForUser: {
|
||||
createWorkspaceForAny: {
|
||||
object: {
|
||||
resource_type: "workspace",
|
||||
organization_id: organizationId,
|
||||
@@ -10,7 +10,7 @@ export const createWorkspaceChecks = (organizationId: string) =>
|
||||
},
|
||||
}) as const;
|
||||
|
||||
export type CreateWSPermissions = Record<
|
||||
export type CreateWorkspacePermissions = Record<
|
||||
keyof ReturnType<typeof createWorkspaceChecks>,
|
||||
boolean
|
||||
>;
|
||||
|
||||
@@ -6,7 +6,10 @@ import { Loader } from "components/Loader/Loader";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { TabLink, Tabs, TabsList } from "components/Tabs/Tabs";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { workspacePermissionChecks } from "modules/permissions/workspaces";
|
||||
import {
|
||||
type WorkspacePermissions,
|
||||
workspacePermissionChecks,
|
||||
} from "modules/permissions/workspaces";
|
||||
import {
|
||||
type FC,
|
||||
type PropsWithChildren,
|
||||
@@ -113,7 +116,9 @@ export const TemplateLayout: FC<PropsWithChildren> = ({
|
||||
template={data.template}
|
||||
activeVersion={data.activeVersion}
|
||||
permissions={data.permissions}
|
||||
workspacePermissions={workspacePermissionsQuery.data}
|
||||
workspacePermissions={
|
||||
workspacePermissionsQuery.data as WorkspacePermissions
|
||||
}
|
||||
onDeleteTemplate={() => {
|
||||
navigate("/templates");
|
||||
}}
|
||||
|
||||
@@ -14,7 +14,7 @@ const meta: Meta<typeof TemplatePageHeader> = {
|
||||
canUpdateTemplate: true,
|
||||
},
|
||||
workspacePermissions: {
|
||||
createWorkspace: true,
|
||||
createWorkspaceForUserID: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -35,7 +35,7 @@ export const CanNotUpdate: Story = {
|
||||
export const CannotCreateWorkspace: Story = {
|
||||
args: {
|
||||
workspacePermissions: {
|
||||
createWorkspace: false,
|
||||
createWorkspaceForUserID: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { linkToTemplate, useLinks } from "modules/navigation";
|
||||
import type { WorkspacePermissions } from "modules/permissions/workspaces";
|
||||
import type { FC } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
@@ -158,7 +159,7 @@ export type TemplatePageHeaderProps = {
|
||||
template: Template;
|
||||
activeVersion: TemplateVersion;
|
||||
permissions: AuthorizationResponse;
|
||||
workspacePermissions: AuthorizationResponse;
|
||||
workspacePermissions: WorkspacePermissions;
|
||||
onDeleteTemplate: () => void;
|
||||
};
|
||||
|
||||
@@ -179,16 +180,17 @@ export const TemplatePageHeader: FC<TemplatePageHeaderProps> = ({
|
||||
<PageHeader
|
||||
actions={
|
||||
<>
|
||||
{!template.deprecated && workspacePermissions.createWorkspace && (
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
component={RouterLink}
|
||||
to={`${templateLink}/workspace`}
|
||||
>
|
||||
Create Workspace
|
||||
</Button>
|
||||
)}
|
||||
{!template.deprecated &&
|
||||
workspacePermissions.createWorkspaceForUserID && (
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
component={RouterLink}
|
||||
to={`${templateLink}/workspace`}
|
||||
>
|
||||
Create Workspace
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{permissions.canUpdateTemplate && (
|
||||
<TemplateMenu
|
||||
|
||||
@@ -76,7 +76,7 @@ export const WithTemplates: Story = {
|
||||
examples: [],
|
||||
workspacePermissions: {
|
||||
[MockTemplate.organization_id]: {
|
||||
createWorkspace: true,
|
||||
createWorkspaceForUserID: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -94,7 +94,7 @@ export const CannotCreateWorkspaces: Story = {
|
||||
...WithTemplates.args,
|
||||
workspacePermissions: {
|
||||
[MockTemplate.organization_id]: {
|
||||
createWorkspace: false,
|
||||
createWorkspaceForUserID: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -160,7 +160,7 @@ const TemplateRow: FC<TemplateRowProps> = ({
|
||||
{template.deprecated ? (
|
||||
<DeprecatedBadge />
|
||||
) : workspacePermissions?.[template.organization_id]
|
||||
?.createWorkspace ? (
|
||||
?.createWorkspaceForUserID ? (
|
||||
<MuiButton
|
||||
size="small"
|
||||
css={styles.actionButton}
|
||||
|
||||
@@ -45,7 +45,7 @@ const WorkspacesPage: FC = () => {
|
||||
|
||||
const templatesQuery = useQuery(templates());
|
||||
|
||||
const orgPermissionsQuery = useQuery(
|
||||
const workspacePermissionsQuery = useQuery(
|
||||
workspacePermissionsByOrganization(
|
||||
templatesQuery.data?.map((template) => template.organization_id),
|
||||
me.id,
|
||||
@@ -54,15 +54,16 @@ const WorkspacesPage: FC = () => {
|
||||
|
||||
// Filter templates based on workspace creation permission
|
||||
const filteredTemplates = useMemo(() => {
|
||||
if (!templatesQuery.data || !orgPermissionsQuery.data) {
|
||||
if (!templatesQuery.data || !workspacePermissionsQuery.data) {
|
||||
return templatesQuery.data;
|
||||
}
|
||||
|
||||
return templatesQuery.data.filter((template) => {
|
||||
const orgPermission = orgPermissionsQuery.data[template.organization_id];
|
||||
return orgPermission?.createWorkspace;
|
||||
const workspacePermission =
|
||||
workspacePermissionsQuery.data[template.organization_id];
|
||||
return workspacePermission?.createWorkspaceForUserID;
|
||||
});
|
||||
}, [templatesQuery.data, orgPermissionsQuery.data]);
|
||||
}, [templatesQuery.data, workspacePermissionsQuery.data]);
|
||||
|
||||
const filterProps = useWorkspacesFilter({
|
||||
searchParamsResult,
|
||||
|
||||
Reference in New Issue
Block a user