Path: blob/main/components/dashboard/src/teams/TeamPolicies.tsx
2501 views
/**1* Copyright (c) 2021 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 { FormEvent, useCallback, useEffect, useState } from "react";8import Alert from "../components/Alert";9import { CheckboxInputField } from "../components/forms/CheckboxInputField";10import { Heading2, Heading3, Subheading } from "../components/typography/headings";11import { useIsOwner } from "../data/organizations/members-query";12import { useOrgSettingsQuery } from "../data/organizations/org-settings-query";13import { useCurrentOrg } from "../data/organizations/orgs-query";14import { useUpdateOrgSettingsMutation } from "../data/organizations/update-org-settings-mutation";15import { OrgSettingsPage } from "./OrgSettingsPage";16import { ConfigurationSettingsField } from "../repositories/detail/ConfigurationSettingsField";17import { useDocumentTitle } from "../hooks/use-document-title";18import { useOrgBillingMode } from "../data/billing-mode/org-billing-mode-query";19import { converter } from "../service/public-api";20import { useToast } from "../components/toasts/Toasts";21import type { PlainMessage } from "@bufbuild/protobuf";22import { WorkspaceTimeoutDuration } from "@gitpod/gitpod-protocol";23import { Link } from "react-router-dom";24import { InputField } from "../components/forms/InputField";25import { TextInput } from "../components/forms/TextInputField";26import { LoadingButton } from "@podkit/buttons/LoadingButton";27import { MaxParallelWorkspaces } from "./policies/MaxParallelWorkspaces";28import { WorkspaceClassesEnterpriseCallout } from "./policies/WorkspaceClassesEnterpriseCallout";29import { EditorOptions } from "./policies/EditorOptions";30import { RolePermissionsRestrictions } from "./policies/RoleRestrictions";31import { OrgWorkspaceClassesOptions } from "./policies/OrgWorkspaceClassesOptions";32import { useDefaultOrgTimeoutQuery } from "../data/organizations/default-org-timeout-query";33import { useInstallationConfiguration } from "../data/installation/installation-config-query";3435export default function TeamPoliciesPage() {36useDocumentTitle("Organization Settings - Policies");37const { toast } = useToast();38const org = useCurrentOrg().data;39const isOwner = useIsOwner();4041const { data: settings, isLoading } = useOrgSettingsQuery();42const updateTeamSettings = useUpdateOrgSettingsMutation();4344const { data: installationConfig } = useInstallationConfiguration();45const isDedicatedInstallation = installationConfig?.isDedicatedInstallation ?? true; // we bias towards being on dedicated so the callout doesn't show when we're not sure4647const billingMode = useOrgBillingMode();48const [workspaceTimeout, setWorkspaceTimeout] = useState<string | undefined>(undefined);49const [allowTimeoutChangeByMembers, setAllowTimeoutChangeByMembers] = useState<boolean | undefined>(undefined);50const [workspaceTimeoutSettingError, setWorkspaceTimeoutSettingError] = useState<string | undefined>(undefined);5152const defaultOrgTimeout = useDefaultOrgTimeoutQuery();5354const handleUpdateTeamSettings = useCallback(55async (newSettings: Partial<PlainMessage<OrganizationSettings>>, options?: { throwMutateError?: boolean }) => {56if (!org?.id) {57throw new Error("no organization selected");58}59if (!isOwner) {60throw new Error("no organization settings change permission");61}62try {63await updateTeamSettings.mutateAsync(newSettings);64setWorkspaceTimeoutSettingError(undefined);65toast("Organization settings updated");66} catch (error) {67if (options?.throwMutateError) {68throw error;69}70toast(`Failed to update organization settings: ${error.message}`);71console.error(error);72}73},74[updateTeamSettings, org?.id, isOwner, toast],75);7677useEffect(() => {78setWorkspaceTimeout(79settings?.timeoutSettings?.inactivity80? converter.toDurationString(settings.timeoutSettings.inactivity)81: undefined,82);83setAllowTimeoutChangeByMembers(!settings?.timeoutSettings?.denyUserTimeouts);84}, [settings?.timeoutSettings]);8586const handleUpdateOrganizationTimeoutSettings = useCallback(87(e: FormEvent<HTMLFormElement>) => {88e.preventDefault();89try {90if (workspaceTimeout) {91WorkspaceTimeoutDuration.validate(workspaceTimeout);92}93} catch (error) {94setWorkspaceTimeoutSettingError(error.message);95return;96}9798// Nothing has changed99if (workspaceTimeout === undefined && allowTimeoutChangeByMembers === undefined) {100return;101}102103handleUpdateTeamSettings({104timeoutSettings: {105inactivity: converter.toDurationOpt(workspaceTimeout),106denyUserTimeouts: !allowTimeoutChangeByMembers,107},108});109},110[workspaceTimeout, allowTimeoutChangeByMembers, handleUpdateTeamSettings],111);112113const isPaidOrDedicated =114billingMode.data?.mode === "none" || (billingMode.data?.mode === "usage-based" && billingMode.data?.paid);115116return (117<>118<OrgSettingsPage>119<div className="space-y-8">120<div>121<Heading2>Policies</Heading2>122<Subheading>123Restrict workspace classes, editors and sharing across your organization.124</Subheading>125</div>126127<ConfigurationSettingsField>128<Heading3>Collaboration and sharing</Heading3>129130{updateTeamSettings.isError && (131<Alert type="error" closable={true} className="mb-2 max-w-xl rounded-md">132<span>Failed to update organization settings: </span>133<span>{updateTeamSettings.error.message || "unknown error"}</span>134</Alert>135)}136137<CheckboxInputField138label="Workspace Sharing"139hint="Allow workspaces created within an Organization to share the workspace with any authenticated user."140checked={!settings?.workspaceSharingDisabled}141onChange={(checked) => handleUpdateTeamSettings({ workspaceSharingDisabled: !checked })}142disabled={isLoading || !isOwner}143/>144</ConfigurationSettingsField>145146<ConfigurationSettingsField>147<Heading3>Workspace timeouts</Heading3>148{!isPaidOrDedicated && (149<Alert type="info" className="my-3">150Setting Workspace timeouts is only available for organizations on a paid plan. Visit{" "}151<Link to={"/billing"} className="gp-link">152Billing153</Link>{" "}154to upgrade your plan.155</Alert>156)}157<form onSubmit={handleUpdateOrganizationTimeoutSettings}>158<InputField159label="Default workspace timeout"160error={workspaceTimeoutSettingError}161hint={162<span>163Use minutes or hours, like <span className="font-semibold">30m</span> or{" "}164<span className="font-semibold">2h</span>. If not set, your organization's165default of <span className="font-semibold">{defaultOrgTimeout}</span> will be166used.167</span>168}169>170<TextInput171value={workspaceTimeout ?? ""}172placeholder="e.g. 30m"173onChange={setWorkspaceTimeout}174disabled={updateTeamSettings.isLoading || !isOwner || !isPaidOrDedicated}175/>176</InputField>177<CheckboxInputField178label="Allow members to change workspace timeouts"179hint="Allow users to change the timeout duration for their workspaces as well as setting a default one in their user settings."180checked={!!allowTimeoutChangeByMembers}181containerClassName="my-4"182onChange={setAllowTimeoutChangeByMembers}183disabled={updateTeamSettings.isLoading || !isOwner || !isPaidOrDedicated}184/>185<LoadingButton186type="submit"187loading={updateTeamSettings.isLoading}188disabled={189!isOwner ||190!isPaidOrDedicated ||191(workspaceTimeout ===192converter.toDurationStringOpt(settings?.timeoutSettings?.inactivity) &&193allowTimeoutChangeByMembers === !settings?.timeoutSettings?.denyUserTimeouts)194}195>196Save197</LoadingButton>198</form>199</ConfigurationSettingsField>200201<MaxParallelWorkspaces202isOwner={isOwner}203isLoading={updateTeamSettings.isLoading}204settings={settings}205handleUpdateTeamSettings={handleUpdateTeamSettings}206isPaidOrDedicated={isPaidOrDedicated}207/>208209<OrgWorkspaceClassesOptions210isOwner={isOwner}211settings={settings}212handleUpdateTeamSettings={handleUpdateTeamSettings}213/>214215{!isDedicatedInstallation && <WorkspaceClassesEnterpriseCallout />}216217<EditorOptions218isOwner={isOwner}219settings={settings}220handleUpdateTeamSettings={handleUpdateTeamSettings}221/>222223<RolePermissionsRestrictions224settings={settings}225isOwner={isOwner}226handleUpdateTeamSettings={handleUpdateTeamSettings}227/>228</div>229</OrgSettingsPage>230</>231);232}233234235