-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support package nodes in the enclave builder ui (#2250)
## Description: This PR adds package nodes to the enclave builder UI. It's based on the backend work added by @tedim52 in #2177. ### Demo This demo has two paths - the first one shows how a package can be added to the enclave builder, and used by a service defined in the builder as a dependency. The second one shows how two packages can be combined in the same enclave - allowing connectivity between them at runtime. https://github.com/kurtosis-tech/kurtosis/assets/4419574/64ed2b91-1565-49ed-841b-c655dd6b28f9 ## Is this change user facing? YES ## References (if applicable): - Discussion on slack. --------- Co-authored-by: Tedi Mitiku <tedi.m52@gmail.com>
- Loading branch information
Showing
41 changed files
with
2,148 additions
and
1,020 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
193 changes: 193 additions & 0 deletions
193
...e-manager/web/packages/app/src/emui/enclaves/components/configuration/PackageSelector.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import { SmallCloseIcon } from "@chakra-ui/icons"; | ||
import { | ||
Button, | ||
Flex, | ||
Icon, | ||
Input, | ||
InputGroup, | ||
InputLeftElement, | ||
InputRightElement, | ||
Spinner, | ||
Text, | ||
} from "@chakra-ui/react"; | ||
import { KurtosisPackage } from "kurtosis-cloud-indexer-sdk"; | ||
import { | ||
FindCommand, | ||
isDefined, | ||
KurtosisAlert, | ||
KurtosisPackageCardHorizontal, | ||
parsePackageUrl, | ||
useKeyboardAction, | ||
useSavedPackages, | ||
} from "kurtosis-ui-components"; | ||
import { debounce } from "lodash"; | ||
import { ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||
import { FiSearch } from "react-icons/fi"; | ||
import { useCatalogContext } from "../../../catalog/CatalogContext"; | ||
|
||
type ExactMatchState = | ||
| { type: "loading"; url: string } | ||
| { type: "loaded"; package: KurtosisPackage } | ||
| { type: "error"; error: string }; | ||
|
||
type PackageSelectorProps = { | ||
onPackageSelected: (kurtosisPackage: KurtosisPackage) => void; | ||
}; | ||
export const PackageSelector = ({ onPackageSelected }: PackageSelectorProps) => { | ||
const searchRef = useRef<HTMLInputElement>(null); | ||
const [searchTerm, setSearchTerm] = useState(""); | ||
|
||
const [exactMatch, setExactMatch] = useState<ExactMatchState>(); | ||
const { catalog, getSinglePackage } = useCatalogContext(); | ||
|
||
const checkSinglePackageDebounced = useMemo( | ||
() => | ||
debounce( | ||
async (packageName: string) => { | ||
const singlePackageResult = await getSinglePackage(packageName); | ||
if (singlePackageResult.isErr) { | ||
setExactMatch({ type: "error", error: singlePackageResult.error }); | ||
return; | ||
} | ||
if (isDefined(singlePackageResult.value.package)) { | ||
setExactMatch({ type: "loaded", package: singlePackageResult.value.package }); | ||
} | ||
}, | ||
1000, | ||
{ trailing: true, leading: false }, | ||
), | ||
[getSinglePackage], | ||
); | ||
|
||
const startCheckSinglePackage = useCallback( | ||
async (packageName: string) => { | ||
let isKurtosisPackageName = false; | ||
try { | ||
parsePackageUrl(packageName); | ||
isKurtosisPackageName = true; | ||
} catch (error: any) { | ||
// This packageName isn't a kurtosis package url | ||
} | ||
if (isKurtosisPackageName) { | ||
setExactMatch({ type: "loading", url: packageName }); | ||
checkSinglePackageDebounced(packageName); | ||
} else { | ||
setExactMatch(undefined); | ||
} | ||
}, | ||
[checkSinglePackageDebounced], | ||
); | ||
|
||
const searchResults = useMemo( | ||
() => | ||
catalog.map((catalog) => | ||
catalog.packages.filter( | ||
(kurtosisPackage) => kurtosisPackage.name.toLowerCase().indexOf(searchTerm.toLowerCase()) >= 0, | ||
), | ||
), | ||
[catalog, searchTerm], | ||
); | ||
|
||
const { savedPackages } = useSavedPackages(); | ||
|
||
const handleSearchTermChange = async (e: ChangeEvent<HTMLInputElement>) => { | ||
setSearchTerm(e.target.value); | ||
}; | ||
|
||
useKeyboardAction(useMemo(() => ({ find: () => searchRef.current?.focus() }), [searchRef])); | ||
|
||
useEffect(() => { | ||
startCheckSinglePackage(searchTerm); | ||
}, [startCheckSinglePackage, searchTerm]); | ||
|
||
if (searchResults.isErr) { | ||
return <KurtosisAlert message={"Unable to load kurtosis packages"} details={searchResults.error} />; | ||
} | ||
|
||
return ( | ||
<> | ||
<InputGroup variant={"solid"} width={"100%"} color={"gray.150"}> | ||
<InputLeftElement> | ||
<Icon as={FiSearch} /> | ||
</InputLeftElement> | ||
<Input | ||
ref={searchRef} | ||
value={searchTerm} | ||
bgColor={"gray.850"} | ||
onChange={handleSearchTermChange} | ||
placeholder={"Search"} | ||
autoFocus | ||
/> | ||
<InputRightElement w={"unset"} mr={"8px"}> | ||
{searchTerm.length > 0 ? ( | ||
<Button variant="ghost" size={"xs"} rightIcon={<SmallCloseIcon />} onClick={() => setSearchTerm("")}> | ||
Clear | ||
</Button> | ||
) : ( | ||
<FindCommand whiteSpace={"nowrap"} pr={"10px"} /> | ||
)} | ||
</InputRightElement> | ||
</InputGroup> | ||
{isDefined(exactMatch) && ( | ||
<Flex flexDirection={"column"} gap={"10px"}> | ||
<Text fontWeight={"semibold"} pt={"16px"} pb={"6px"}> | ||
Exact Match | ||
</Text> | ||
{exactMatch.type === "loading" && ( | ||
<Flex flexDirection={"column"} alignItems={"center"}> | ||
<Spinner /> | ||
<Text>Looking for a Kurtosis Package at {exactMatch.url}</Text> | ||
</Flex> | ||
)} | ||
{exactMatch.type === "loaded" && ( | ||
<KurtosisPackageCardHorizontal | ||
kurtosisPackage={exactMatch.package} | ||
onClick={() => onPackageSelected(exactMatch.package)} | ||
/> | ||
)} | ||
{exactMatch.type === "error" && ( | ||
<KurtosisAlert message={"Error looking up package"} details={exactMatch.error} /> | ||
)} | ||
</Flex> | ||
)} | ||
{(searchTerm.length > 0 || savedPackages.length === 0) && ( | ||
<Flex flexDirection={"column"} gap={"10px"}> | ||
<Text fontWeight={"semibold"} pt={"16px"} pb={"6px"}> | ||
{searchTerm.length === 0 ? "All Packages" : "Search Results"} | ||
</Text> | ||
{searchResults.value.map((kurtosisPackage) => ( | ||
<KurtosisPackageCardHorizontal | ||
key={kurtosisPackage.name} | ||
kurtosisPackage={kurtosisPackage} | ||
onClick={() => onPackageSelected(kurtosisPackage)} | ||
/> | ||
))} | ||
</Flex> | ||
)} | ||
{searchTerm.length === 0 && savedPackages.length > 0 && ( | ||
<Flex flexDirection={"column"} gap={"10px"}> | ||
<Text fontWeight={"semibold"} pt={"16px"} pb={"6px"}> | ||
Saved | ||
</Text> | ||
{savedPackages.map((kurtosisPackage) => ( | ||
<KurtosisPackageCardHorizontal | ||
key={kurtosisPackage.name} | ||
kurtosisPackage={kurtosisPackage} | ||
onClick={() => onPackageSelected(kurtosisPackage)} | ||
/> | ||
))} | ||
<Text fontWeight={"semibold"} pt={"16px"} pb={"6px"}> | ||
All Packages | ||
</Text> | ||
{searchResults.value.map((kurtosisPackage) => ( | ||
<KurtosisPackageCardHorizontal | ||
key={kurtosisPackage.name} | ||
kurtosisPackage={kurtosisPackage} | ||
onClick={() => onPackageSelected(kurtosisPackage)} | ||
/> | ||
))} | ||
</Flex> | ||
)} | ||
</> | ||
); | ||
}; |
Oops, something went wrong.