From 37e66ce34ab125f8fb4952d6041a2ca159ca2603 Mon Sep 17 00:00:00 2001 From: --global Date: Thu, 14 Nov 2024 09:27:03 +0100 Subject: [PATCH] fix(desktop): show provider options for workspaces with old pro provider --- .../src/views/Workspaces/WorkspaceCard.tsx | 172 +++++++++++++++++- .../views/Workspaces/WorkspaceControls.tsx | 23 ++- 2 files changed, 190 insertions(+), 5 deletions(-) diff --git a/desktop/src/views/Workspaces/WorkspaceCard.tsx b/desktop/src/views/Workspaces/WorkspaceCard.tsx index 798d404df..580c04eb8 100644 --- a/desktop/src/views/Workspaces/WorkspaceCard.tsx +++ b/desktop/src/views/Workspaces/WorkspaceCard.tsx @@ -1,7 +1,22 @@ -import { Card, CardHeader, Text } from "@chakra-ui/react" -import { useCallback, useMemo, useState } from "react" +import { + Box, + Card, + CardHeader, + Icon, + List, + ListItem, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + Text, + useDisclosure, +} from "@chakra-ui/react" +import { useCallback, useMemo, useRef, useState } from "react" import { useNavigate } from "react-router" -import { WorkspaceCardHeader } from "../../components" +import { IconTag, WorkspaceCardHeader } from "../../components" import { TActionID, useProvider, @@ -18,10 +33,15 @@ import { useStopWorkspaceModal, } from "../../lib" import { Routes } from "../../routes" -import { TWorkspace, TWorkspaceID } from "../../types" +import { TProvider, TWorkspace, TWorkspaceID } from "../../types" import { useIDEs } from "../../useIDEs" import { WorkspaceControls } from "./WorkspaceControls" import { WorkspaceStatusBadge } from "./WorkspaceStatusBadge" +import { ConfigureProviderOptionsForm } from "../Providers" +import { Template } from "@/icons" +import { HiServerStack } from "react-icons/hi2" +import { TOptionWithID, mergeOptionDefinitions } from "../Providers/helpers" +import { processDisplayOptions } from "../Providers/AddProvider/useProviderOptions" type TWorkspaceCardProps = Readonly<{ workspaceID: TWorkspaceID @@ -30,6 +50,7 @@ type TWorkspaceCardProps = Readonly<{ }> export function WorkspaceCard({ workspaceID, isSelected, onSelectionChange }: TWorkspaceCardProps) { + const changeOptionsModalBodyRef = useRef(null) const settings = useSettings() const navigate = useNavigate() const { ides, defaultIDE } = useIDEs() @@ -85,6 +106,11 @@ export function WorkspaceCard({ workspaceID, isSelected, onSelectionChange }: TW [navigateToAction, workspace] ) ) + const { + isOpen: isChangeOptionsOpen, + onOpen: handleChangeOptionsClicked, + onClose: onChangeOptionsClose, + } = useDisclosure() const [provider] = useProvider(workspace.data?.provider?.name) const [ideName, setIdeName] = useState(() => { @@ -134,12 +160,37 @@ export function WorkspaceCard({ workspaceID, isSelected, onSelectionChange }: TW return undefined }, [navigateToAction, workspace, workspaceActions]) + const handleChangeOptionsFinishClicked = (extraProviderOptions: Record) => { + // diff against current workspace options + let changedOptions: Record | undefined = undefined + if (Object.keys(extraProviderOptions).length > 0) { + changedOptions = {} + const workspaceOptions = workspace.data?.provider?.options ?? {} + for (const [k, v] of Object.entries(extraProviderOptions)) { + // check if current workspace option doesn't contain option or it does but value is different + if (!workspaceOptions[k] || workspaceOptions[k]?.value !== v) { + changedOptions[k] = v + } + } + } + const actionID = workspace.start({ + id: workspaceID, + providerConfig: { options: changedOptions }, + }) + onChangeOptionsClose() + navigateToAction(actionID) + } + const isLoading = workspace.current?.status === "pending" if (workspace.data === undefined) { return null } + const maybeRunnerName = getRunnerName(workspace.data, provider) + const maybeTemplate = getTemplate(workspace.data, provider) + const maybeTemplateOptions = getTemplateOptions(workspace.data, provider) + return ( <> }> + {maybeTemplate && ( + } + label={maybeTemplate} + info={ + + Using {maybeTemplate} template with options:
+ {maybeTemplateOptions.length > 0 ? ( + + {maybeTemplateOptions.map((opt) => ( + + {opt.value} + ({opt.displayName || opt.id}) + + ))} + + ) : ( + "No options configured" + )} +
+ } + /> + )} + {maybeRunnerName && ( + } + label={maybeRunnerName} + info={`Running on ${maybeRunnerName}`} + /> + )}
@@ -208,6 +295,83 @@ export function WorkspaceCard({ workspaceID, isSelected, onSelectionChange }: TW {rebuildModal} {deleteModal} {stopModal} + + + + + Change Options + + + {workspace.data.provider?.name ? ( + + ) : ( + <>Unable to find provider for this workspace + )} + + + ) } + +function getRunnerName(workspace: TWorkspace, provider: TProvider | undefined): string | undefined { + const options = mergeOptionDefinitions( + workspace.provider?.options ?? {}, + provider?.config?.options ?? {} + ) + const maybeRunnerOption = options["LOFT_RUNNER"] + if (!maybeRunnerOption) { + return undefined + } + const value = maybeRunnerOption.value + + return maybeRunnerOption.enum?.find((e) => e.value === value)?.displayName ?? value ?? undefined +} + +function getTemplate(workspace: TWorkspace, provider: TProvider | undefined): string | undefined { + const options = mergeOptionDefinitions( + workspace.provider?.options ?? {}, + provider?.config?.options ?? {} + ) + const maybeTemplateOption = options["LOFT_TEMPLATE"] + if (!maybeTemplateOption) { + return undefined + } + const value = maybeTemplateOption.value + + return maybeTemplateOption.enum?.find((e) => e.value === value)?.displayName ?? value ?? undefined +} + +function getTemplateOptions( + workspace: TWorkspace, + provider: TProvider | undefined +): readonly TOptionWithID[] { + const options = mergeOptionDefinitions( + workspace.provider?.options ?? {}, + provider?.config?.options ?? {} + ) + const displayOptions = processDisplayOptions(options, [], true) + + // shouldn't have groups here as we passed in empty array earlier + return [...displayOptions.required, ...displayOptions.other].filter( + (opt) => opt.id !== "LOFT_TEMPLATE" + ) +} diff --git a/desktop/src/views/Workspaces/WorkspaceControls.tsx b/desktop/src/views/Workspaces/WorkspaceControls.tsx index e0bf5a75d..397fffc3a 100644 --- a/desktop/src/views/Workspaces/WorkspaceControls.tsx +++ b/desktop/src/views/Workspaces/WorkspaceControls.tsx @@ -23,7 +23,16 @@ import { HiOutlineCode, HiShare } from "react-icons/hi" import { client } from "../../client" import { IDEIcon } from "../../components" import { TActionID, useProInstances } from "../../contexts" -import { ArrowCycle, ArrowPath, CommandLine, Ellipsis, Pause, Play, Trash } from "../../icons" +import { + ArrowCycle, + ArrowPath, + CommandLine, + Ellipsis, + Pause, + Play, + Stack3D, + Trash, +} from "../../icons" import { getIDEDisplayName, useHover } from "../../lib" import { TIDE, TIDEs, TProInstance, TProvider, TWorkspace, TWorkspaceID } from "../../types" @@ -42,6 +51,7 @@ type TWorkspaceControlsProps = Readonly<{ onDeleteClicked: VoidFunction onStopClicked: VoidFunction onLogsClicked: VoidFunction + onChangeOptionsClicked?: VoidFunction }> export function WorkspaceControls({ id, @@ -58,6 +68,7 @@ export function WorkspaceControls({ onDeleteClicked, onStopClicked, onLogsClicked, + onChangeOptionsClicked, }: TWorkspaceControlsProps) { const [[proInstances]] = useProInstances() const proInstance = useMemo(() => { @@ -89,6 +100,8 @@ export function WorkspaceControls({ "Cannot open this workspace because it is busy. If this doesn't change, try to force delete and recreate it." const [isStartWithHovering, startWithRef] = useHover() const [isPopoverHovering, popoverContentRef] = useHover() + const isChangeOptionsEnabled = + workspace.data?.provider?.options != null && proInstance !== undefined return ( @@ -176,6 +189,14 @@ export function WorkspaceControls({ isDisabled={isOpenDisabled || isLoading}> Reset + {isChangeOptionsEnabled && ( + } + onClick={onChangeOptionsClicked} + isDisabled={isOpenDisabled || isLoading}> + Change Options + + )} {isShareEnabled && ( } onClick={handleShareClicked}> Share