Path: blob/main/components/dashboard/src/teams/policies/OrgWorkspaceClassesOptions.tsx
2501 views
/**1* Copyright (c) 2025 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import { OrganizationSettings } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";7import { Button } from "@podkit/buttons/Button";8import { useMutation } from "@tanstack/react-query";9import { useState, useMemo } from "react";10import { Heading3, Subheading } from "../../components/typography/headings";11import {12WorkspaceClassesModifyModalProps,13WorkspaceClassesOptions,14WorkspaceClassesModifyModal,15} from "../../components/WorkspaceClassesOptions";16import { useAllowedWorkspaceClassesMemo } from "../../data/workspaces/workspace-classes-query";17import { ConfigurationSettingsField } from "../../repositories/detail/ConfigurationSettingsField";1819interface OrgWorkspaceClassesOptionsProps {20isOwner: boolean;21settings?: OrganizationSettings;22handleUpdateTeamSettings: (23newSettings: Partial<OrganizationSettings>,24options?: { throwMutateError?: boolean },25) => Promise<void>;26}27export const OrgWorkspaceClassesOptions = ({28isOwner,29settings,30handleUpdateTeamSettings,31}: OrgWorkspaceClassesOptionsProps) => {32const [showModal, setShowModal] = useState(false);33const { data: allowedClassesInOrganization, isLoading: isLoadingClsInOrg } = useAllowedWorkspaceClassesMemo(34undefined,35{36filterOutDisabled: true,37ignoreScope: ["configuration"],38},39);40const { data: allowedClassesInInstallation, isLoading: isLoadingClsInInstall } = useAllowedWorkspaceClassesMemo(41undefined,42{43filterOutDisabled: true,44ignoreScope: ["organization", "configuration"],45},46);4748const restrictedWorkspaceClasses = useMemo(() => {49const allowedList = settings?.allowedWorkspaceClasses ?? [];50if (allowedList.length === 0) {51return [];52}53return allowedClassesInInstallation.filter((cls) => !allowedList.includes(cls.id)).map((cls) => cls.id);54}, [settings?.allowedWorkspaceClasses, allowedClassesInInstallation]);5556const updateMutation: WorkspaceClassesModifyModalProps["updateMutation"] = useMutation({57mutationFn: async ({ restrictedWorkspaceClasses }) => {58let allowedWorkspaceClasses = allowedClassesInInstallation.map((e) => e.id);59if (restrictedWorkspaceClasses.length > 0) {60allowedWorkspaceClasses = allowedWorkspaceClasses.filter(61(e) => !restrictedWorkspaceClasses.includes(e),62);63}64const allAllowed = allowedClassesInInstallation.every((e) => allowedWorkspaceClasses.includes(e.id));65if (allAllowed) {66// empty means allow all classes67allowedWorkspaceClasses = [];68}69await handleUpdateTeamSettings({ allowedWorkspaceClasses }, { throwMutateError: true });70},71});7273return (74<ConfigurationSettingsField>75<Heading3>Available workspace classes</Heading3>76<Subheading>77Limit the available workspace classes in your organization. Requires{" "}78<span className="font-medium">Owner</span> permissions to change.79</Subheading>8081<WorkspaceClassesOptions82isLoading={isLoadingClsInOrg}83className="mt-4"84classes={allowedClassesInOrganization}85/>8687{isOwner && (88<Button className="mt-6" onClick={() => setShowModal(true)}>89Manage Classes90</Button>91)}9293{showModal && (94<WorkspaceClassesModifyModal95isLoading={isLoadingClsInInstall}96showSetDefaultButton={false}97showSwitchTitle={false}98restrictedWorkspaceClasses={restrictedWorkspaceClasses}99allowedClasses={allowedClassesInInstallation}100updateMutation={updateMutation}101onClose={() => setShowModal(false)}102/>103)}104</ConfigurationSettingsField>105);106};107108109