diff --git a/enclave-manager/web/src/client/packageIndexer/KurtosisPackageIndexerClient.ts b/enclave-manager/web/src/client/packageIndexer/KurtosisPackageIndexerClient.ts index 33156813e0..fbdbf89662 100644 --- a/enclave-manager/web/src/client/packageIndexer/KurtosisPackageIndexerClient.ts +++ b/enclave-manager/web/src/client/packageIndexer/KurtosisPackageIndexerClient.ts @@ -3,7 +3,7 @@ import { createConnectTransport } from "@connectrpc/connect-web"; import { asyncResult } from "../../utils"; import { KURTOSIS_PACKAGE_INDEXER_URL } from "../constants"; import { KurtosisPackageIndexer } from "./api/kurtosis_package_indexer_connect"; -import { ReadPackageRequest } from "./api/kurtosis_package_indexer_pb"; +import { PackageRepository, ReadPackageRequest } from "./api/kurtosis_package_indexer_pb"; export class KurtosisPackageIndexerClient { private client: PromiseClient; @@ -21,25 +21,25 @@ export class KurtosisPackageIndexerClient { }); }; + parsePackageUrl(packageUrl: string) { + const components = packageUrl.split("/"); + if (components.length < 3) { + throw Error(`Illegal url, invalid number of components: ${packageUrl}`); + } + if (components[1].length < 1 || components[2].length < 1) { + throw Error(`Illegal url, empty components: ${packageUrl}`); + } + return new PackageRepository({ + baseUrl: "github.com", + owner: components[1], + name: components[2], + rootPath: components.filter((v, i) => i > 2 && v.length > 0).join("/") + "/", + }); + } + readPackage = async (packageUrl: string) => { return asyncResult(() => { - const components = packageUrl.split("/"); - if (components.length < 3) { - throw Error(`Illegal url, invalid number of components: ${packageUrl}`); - } - if (components[1].length < 1 || components[2].length < 1) { - throw Error(`Illegal url, empty components: ${packageUrl}`); - } - return this.client.readPackage( - new ReadPackageRequest({ - repositoryMetadata: { - baseUrl: "github.com", - owner: components[1], - name: components[2], - rootPath: components.filter((v, i) => i > 2 && v.length > 0).join("/") + "/", - }, - }), - ); + return this.client.readPackage(new ReadPackageRequest({ repositoryMetadata: this.parsePackageUrl(packageUrl) })); }); }; } diff --git a/enclave-manager/web/src/client/packageIndexer/api/kurtosis_package_indexer_connect.ts b/enclave-manager/web/src/client/packageIndexer/api/kurtosis_package_indexer_connect.ts index 6e7255cfff..e44e8f5a7a 100644 --- a/enclave-manager/web/src/client/packageIndexer/api/kurtosis_package_indexer_connect.ts +++ b/enclave-manager/web/src/client/packageIndexer/api/kurtosis_package_indexer_connect.ts @@ -48,5 +48,6 @@ export const KurtosisPackageIndexer = { O: ReadPackageResponse, kind: MethodKind.Unary, }, - }, + } } as const; + diff --git a/enclave-manager/web/src/client/packageIndexer/api/kurtosis_package_indexer_pb.ts b/enclave-manager/web/src/client/packageIndexer/api/kurtosis_package_indexer_pb.ts index 6551cc9cbf..80661a1471 100644 --- a/enclave-manager/web/src/client/packageIndexer/api/kurtosis_package_indexer_pb.ts +++ b/enclave-manager/web/src/client/packageIndexer/api/kurtosis_package_indexer_pb.ts @@ -284,6 +284,11 @@ export class PackageArg extends Message { */ typeV2?: PackageArgumentType; + /** + * @generated from field: optional string defaultValue = 6; + */ + defaultValue?: string; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -296,6 +301,7 @@ export class PackageArg extends Message { { no: 2, name: "is_required", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, { no: 4, name: "description", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "typeV2", kind: "message", T: PackageArgumentType }, + { no: 6, name: "defaultValue", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): PackageArg { diff --git a/enclave-manager/web/src/components/FormatDateTime.tsx b/enclave-manager/web/src/components/FormatDateTime.tsx index fe7765ceed..c1e3b8c1b1 100644 --- a/enclave-manager/web/src/components/FormatDateTime.tsx +++ b/enclave-manager/web/src/components/FormatDateTime.tsx @@ -31,7 +31,7 @@ export const FormatDateTime = ({ dateTime, format, ...textProps }: FormatDateTim } return ( - + {formattedDateTime} diff --git a/enclave-manager/web/src/components/KurtosisBreadcrumbs.tsx b/enclave-manager/web/src/components/KurtosisBreadcrumbs.tsx index f1af799984..755bb67f83 100644 --- a/enclave-manager/web/src/components/KurtosisBreadcrumbs.tsx +++ b/enclave-manager/web/src/components/KurtosisBreadcrumbs.tsx @@ -1,14 +1,35 @@ import { ChevronRightIcon } from "@chakra-ui/icons"; -import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, Flex } from "@chakra-ui/react"; -import { useEffect, useState } from "react"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + Button, + ButtonGroup, + Flex, + Icon, + IconButton, + Menu, + MenuButton, + MenuItem, + MenuList, +} from "@chakra-ui/react"; +import { ReactElement, useEffect, useState } from "react"; +import { MdFilterList } from "react-icons/md"; import { Link, Params, UIMatch, useMatches } from "react-router-dom"; import { EmuiAppState, useEmuiAppContext } from "../emui/EmuiAppContext"; import { isDefined } from "../utils"; import { RemoveFunctions } from "../utils/types"; +type KurtosisBreadcrumbMenuItem = { + name: string; + destination: string; + icon?: ReactElement; +}; + export type KurtosisBreadcrumb = { name: string; destination: string; + alternatives?: KurtosisBreadcrumbMenuItem[]; }; export const KurtosisBreadcrumbs = () => { @@ -50,17 +71,9 @@ export const KurtosisBreadcrumbs = () => { return ( }> - {matchCrumbs.map(({ name, destination }, i, arr) => ( + {matchCrumbs.map((crumb, i, arr) => ( - - {i === arr.length - 1 ? ( - name - ) : ( - - )} - + ))} @@ -68,3 +81,47 @@ export const KurtosisBreadcrumbs = () => { ); }; + +type KurtosisBreadcrumbItemProps = KurtosisBreadcrumb & { + isLastItem: boolean; +}; + +const KurtosisBreadcrumbItem = ({ name, destination, alternatives, isLastItem }: KurtosisBreadcrumbItemProps) => { + if (isLastItem) { + return {name}; + } + + const baseLink = ( + + + + ); + + if (isDefined(alternatives) && alternatives.length > 0) { + // Render with menu + return ( + + {baseLink} + + } + size={"sm"} + /> + + {alternatives.map(({ name, destination, icon }) => ( + + {name} + + ))} + + + + ); + } + return baseLink; +}; diff --git a/enclave-manager/web/src/components/KurtosisThemeProvider.tsx b/enclave-manager/web/src/components/KurtosisThemeProvider.tsx index 4f82b28c59..a498bdbcdb 100644 --- a/enclave-manager/web/src/components/KurtosisThemeProvider.tsx +++ b/enclave-manager/web/src/components/KurtosisThemeProvider.tsx @@ -97,8 +97,8 @@ const theme = extendTheme({ }, variants: { outline: (props: StyleFunctionProps) => ({ - _hover: { borderColor: `${props.colorScheme}.400`, bg: `gray.700` }, - _active: { bg: `gray.800` }, + _hover: { borderColor: `${props.colorScheme}.400`, bg: `gray.650` }, + _active: { bg: `gray.700` }, color: `${props.colorScheme}.400`, borderColor: "gray.300", }), diff --git a/enclave-manager/web/src/components/enclaves/CreateEnclaveButton.tsx b/enclave-manager/web/src/components/enclaves/CreateEnclaveButton.tsx index 18a77378d0..9bd92f5d5e 100644 --- a/enclave-manager/web/src/components/enclaves/CreateEnclaveButton.tsx +++ b/enclave-manager/web/src/components/enclaves/CreateEnclaveButton.tsx @@ -1,4 +1,4 @@ -import { Button, Menu, MenuButton } from "@chakra-ui/react"; +import { Button, Menu, MenuButton, Tooltip } from "@chakra-ui/react"; import { FiPlus } from "react-icons/fi"; import { useNavigate } from "react-router-dom"; import { KURTOSIS_CREATE_ENCLAVE_URL_ARG } from "../constants"; @@ -8,15 +8,17 @@ export const CreateEnclaveButton = () => { return ( <> - } - size={"md"} - onClick={() => navigate(`#${KURTOSIS_CREATE_ENCLAVE_URL_ARG}`)} - > - New Enclave - + + } + size={"md"} + onClick={() => navigate(`#${KURTOSIS_CREATE_ENCLAVE_URL_ARG}`)} + > + New Enclave + + {/**/} {/* navigate(`#${KURTOSIS_CREATE_ENCLAVE_URL_ARG}`)} icon={}>*/} {/* Manual*/} diff --git a/enclave-manager/web/src/components/enclaves/EditEnclaveButton.tsx b/enclave-manager/web/src/components/enclaves/EditEnclaveButton.tsx index 5c2ab103da..25f1119f07 100644 --- a/enclave-manager/web/src/components/enclaves/EditEnclaveButton.tsx +++ b/enclave-manager/web/src/components/enclaves/EditEnclaveButton.tsx @@ -40,9 +40,14 @@ export const EditEnclaveButton = ({ enclave }: EditEnclaveButtonProps) => { return ( <> - + + + {showPackageLoader && ( )} diff --git a/enclave-manager/web/src/components/enclaves/configuration/KurtosisPackageArgumentInput.tsx b/enclave-manager/web/src/components/enclaves/configuration/KurtosisPackageArgumentInput.tsx index 748e4dc314..9387b19982 100644 --- a/enclave-manager/web/src/components/enclaves/configuration/KurtosisPackageArgumentInput.tsx +++ b/enclave-manager/web/src/components/enclaves/configuration/KurtosisPackageArgumentInput.tsx @@ -35,6 +35,7 @@ export const KurtosisPackageArgumentInput = ({ argument, disabled }: KurtosisPac subType1={argument.typeV2?.innerType1} subType2={argument.typeV2?.innerType2} name={fieldName} + placeholder={argument.defaultValue} isRequired={argument.isRequired} /> diff --git a/enclave-manager/web/src/components/enclaves/configuration/inputs/BooleanArgumentInput.tsx b/enclave-manager/web/src/components/enclaves/configuration/inputs/BooleanArgumentInput.tsx index 6965505649..b28b6ea7ac 100644 --- a/enclave-manager/web/src/components/enclaves/configuration/inputs/BooleanArgumentInput.tsx +++ b/enclave-manager/web/src/components/enclaves/configuration/inputs/BooleanArgumentInput.tsx @@ -1,8 +1,8 @@ import { Radio, RadioGroup, Stack, Switch } from "@chakra-ui/react"; import { useEnclaveConfigurationFormContext } from "../EnclaveConfigurationForm"; -import { KurtosisArgumentTypeInputProps } from "./KurtosisArgumentTypeInput"; +import { KurtosisArgumentTypeInputImplProps } from "./KurtosisArgumentTypeInput"; -type BooleanArgumentInputProps = Omit & { +type BooleanArgumentInputProps = KurtosisArgumentTypeInputImplProps & { inputType?: "radio" | "switch"; }; diff --git a/enclave-manager/web/src/components/enclaves/configuration/inputs/DictArgumentInput.tsx b/enclave-manager/web/src/components/enclaves/configuration/inputs/DictArgumentInput.tsx index 21fa131026..fa9edac5c0 100644 --- a/enclave-manager/web/src/components/enclaves/configuration/inputs/DictArgumentInput.tsx +++ b/enclave-manager/web/src/components/enclaves/configuration/inputs/DictArgumentInput.tsx @@ -8,9 +8,9 @@ import { CopyButton } from "../../../CopyButton"; import { PasteButton } from "../../../PasteButton"; import { KurtosisArgumentSubtypeFormControl } from "../KurtosisArgumentFormControl"; import { ConfigureEnclaveForm } from "../types"; -import { KurtosisArgumentTypeInput, KurtosisArgumentTypeInputProps } from "./KurtosisArgumentTypeInput"; +import { KurtosisArgumentTypeInput, KurtosisArgumentTypeInputImplProps } from "./KurtosisArgumentTypeInput"; -type DictArgumentInputProps = Omit & { +type DictArgumentInputProps = KurtosisArgumentTypeInputImplProps & { keyType: ArgumentValueType; valueType: ArgumentValueType; }; diff --git a/enclave-manager/web/src/components/enclaves/configuration/inputs/IntegerArgumentInput.tsx b/enclave-manager/web/src/components/enclaves/configuration/inputs/IntegerArgumentInput.tsx index e78ccbdcec..27d1aade30 100644 --- a/enclave-manager/web/src/components/enclaves/configuration/inputs/IntegerArgumentInput.tsx +++ b/enclave-manager/web/src/components/enclaves/configuration/inputs/IntegerArgumentInput.tsx @@ -1,9 +1,9 @@ import { Input } from "@chakra-ui/react"; import { isDefined } from "../../../../utils"; import { useEnclaveConfigurationFormContext } from "../EnclaveConfigurationForm"; -import { KurtosisArgumentTypeInputProps } from "./KurtosisArgumentTypeInput"; +import { KurtosisArgumentTypeInputImplProps } from "./KurtosisArgumentTypeInput"; -export const IntegerArgumentInput = (props: Omit) => { +export const IntegerArgumentInput = (props: KurtosisArgumentTypeInputImplProps) => { const { register } = useEnclaveConfigurationFormContext(); return ( @@ -22,6 +22,7 @@ export const IntegerArgumentInput = (props: Omit diff --git a/enclave-manager/web/src/components/enclaves/configuration/inputs/JSONArgumentInput.tsx b/enclave-manager/web/src/components/enclaves/configuration/inputs/JSONArgumentInput.tsx index dab144a272..fe5fb66a52 100644 --- a/enclave-manager/web/src/components/enclaves/configuration/inputs/JSONArgumentInput.tsx +++ b/enclave-manager/web/src/components/enclaves/configuration/inputs/JSONArgumentInput.tsx @@ -1,9 +1,9 @@ import { Controller } from "react-hook-form"; import { isDefined, stringifyError } from "../../../../utils"; import { CodeEditor } from "../../../CodeEditor"; -import { KurtosisArgumentTypeInputProps } from "./KurtosisArgumentTypeInput"; +import { KurtosisArgumentTypeInputImplProps } from "./KurtosisArgumentTypeInput"; -export const JSONArgumentInput = (props: Omit) => { +export const JSONArgumentInput = (props: KurtosisArgumentTypeInputImplProps) => { return ( } diff --git a/enclave-manager/web/src/components/enclaves/configuration/inputs/KurtosisArgumentTypeInput.tsx b/enclave-manager/web/src/components/enclaves/configuration/inputs/KurtosisArgumentTypeInput.tsx index 97f1913e37..e4b43e9a29 100644 --- a/enclave-manager/web/src/components/enclaves/configuration/inputs/KurtosisArgumentTypeInput.tsx +++ b/enclave-manager/web/src/components/enclaves/configuration/inputs/KurtosisArgumentTypeInput.tsx @@ -10,11 +10,12 @@ import { JSONArgumentInput } from "./JSONArgumentInput"; import { ListArgumentInput } from "./ListArgumentInput"; import { StringArgumentInput } from "./StringArgumentInput"; -export type KurtosisArgumentTypeInputProps = { +type KurtosisArgumentTypeInputProps = { type?: ArgumentValueType; subType1?: ArgumentValueType; subType2?: ArgumentValueType; name: FieldPath; + placeholder?: string; isRequired?: boolean; validate?: (value: any) => string | undefined; disabled?: boolean; @@ -22,90 +23,46 @@ export type KurtosisArgumentTypeInputProps = { size?: string; }; +export type KurtosisArgumentTypeInputImplProps = Omit; + export const KurtosisArgumentTypeInput = ({ type, subType1, subType2, name, + placeholder, isRequired, validate, disabled, width, size, }: KurtosisArgumentTypeInputProps) => { + const childProps: KurtosisArgumentTypeInputImplProps = { + name, + placeholder, + isRequired, + validate, + disabled, + width, + size, + }; + switch (type) { case ArgumentValueType.INTEGER: - return ( - - ); + return ; case ArgumentValueType.DICT: assertDefined(subType1, `innerType1 was not defined on DICT argument ${name}`); assertDefined(subType2, `innerType2 was not defined on DICT argument ${name}`); - return ( - - ); + return ; case ArgumentValueType.LIST: assertDefined(subType1, `innerType1 was not defined on DICT argument ${name}`); - return ( - - ); + return ; case ArgumentValueType.BOOL: - return ( - - ); + return ; case ArgumentValueType.STRING: - return ( - - ); + return ; case ArgumentValueType.JSON: default: - return ( - - ); + return ; } }; diff --git a/enclave-manager/web/src/components/enclaves/configuration/inputs/ListArgumentInput.tsx b/enclave-manager/web/src/components/enclaves/configuration/inputs/ListArgumentInput.tsx index 06567bf6d3..8818ac9d04 100644 --- a/enclave-manager/web/src/components/enclaves/configuration/inputs/ListArgumentInput.tsx +++ b/enclave-manager/web/src/components/enclaves/configuration/inputs/ListArgumentInput.tsx @@ -8,9 +8,9 @@ import { CopyButton } from "../../../CopyButton"; import { PasteButton } from "../../../PasteButton"; import { KurtosisArgumentSubtypeFormControl } from "../KurtosisArgumentFormControl"; import { ConfigureEnclaveForm } from "../types"; -import { KurtosisArgumentTypeInput, KurtosisArgumentTypeInputProps } from "./KurtosisArgumentTypeInput"; +import { KurtosisArgumentTypeInput, KurtosisArgumentTypeInputImplProps } from "./KurtosisArgumentTypeInput"; -type ListArgumentInputProps = Omit & { +type ListArgumentInputProps = KurtosisArgumentTypeInputImplProps & { valueType: ArgumentValueType; }; diff --git a/enclave-manager/web/src/components/enclaves/configuration/inputs/StringArgumentInput.tsx b/enclave-manager/web/src/components/enclaves/configuration/inputs/StringArgumentInput.tsx index b0a2e9fa60..0deecbc9ed 100644 --- a/enclave-manager/web/src/components/enclaves/configuration/inputs/StringArgumentInput.tsx +++ b/enclave-manager/web/src/components/enclaves/configuration/inputs/StringArgumentInput.tsx @@ -1,13 +1,14 @@ import { Input } from "@chakra-ui/react"; import { useEnclaveConfigurationFormContext } from "../EnclaveConfigurationForm"; -import { KurtosisArgumentTypeInputProps } from "./KurtosisArgumentTypeInput"; +import { KurtosisArgumentTypeInputImplProps } from "./KurtosisArgumentTypeInput"; -export const StringArgumentInput = (props: Omit) => { +export const StringArgumentInput = (props: KurtosisArgumentTypeInputImplProps) => { const { register } = useEnclaveConfigurationFormContext(); return ( diff --git a/enclave-manager/web/src/components/enclaves/logs/LogLine.tsx b/enclave-manager/web/src/components/enclaves/logs/LogLine.tsx index 01feb112aa..d968077e04 100644 --- a/enclave-manager/web/src/components/enclaves/logs/LogLine.tsx +++ b/enclave-manager/web/src/components/enclaves/logs/LogLine.tsx @@ -56,6 +56,7 @@ export const LogLine = ({ timestamp, message, status }: LogLineProps) => { return ( <> - + + + { + const kurtosisIndexer = useKurtosisPackageIndexerClient(); + if (!isDefined(source)) { return Unknown; } @@ -24,13 +27,36 @@ export const EnclaveSourceButton = ({ source, ...buttonProps }: EnclaveSourcePro ); if (source.startsWith("github.com/")) { - button = ( - - - - ); + const repositoryResult = wrapResult(() => kurtosisIndexer.parsePackageUrl(source)); + if (repositoryResult.isOk) { + const repository = repositoryResult.value; + const url = `https://${repository.baseUrl}/${repository.owner}/${repository.name}${ + isDefined(repository.rootPath) && repository.rootPath !== "/" ? "/tree/main/" + repository.rootPath : "" + }`; + + button = ( + + + + ); + } else { + button = ( + + + + + + ); + } } return ( diff --git a/enclave-manager/web/src/components/enclaves/widgets/PortsSummary.tsx b/enclave-manager/web/src/components/enclaves/widgets/PortsSummary.tsx index 250f1ca304..6df7e7f82a 100644 --- a/enclave-manager/web/src/components/enclaves/widgets/PortsSummary.tsx +++ b/enclave-manager/web/src/components/enclaves/widgets/PortsSummary.tsx @@ -1,5 +1,4 @@ import { - Button, Card, Flex, Popover, @@ -8,6 +7,7 @@ import { Table, Tbody, Td, + Text, Th, Thead, Tr, @@ -24,9 +24,9 @@ export const PortsSummary = ({ privatePorts, publicPorts }: PortsSummaryProps) = return ( - + @@ -53,6 +53,7 @@ const PortTable = ({ privatePorts, publicPorts }: PortTableProps) => { + @@ -63,11 +64,12 @@ const PortTable = ({ privatePorts, publicPorts }: PortTableProps) => { .sort(([name1, p1], [name2, p2]) => p1.number - p2.number) .map(([name, port], i) => ( + - + ))} diff --git a/enclave-manager/web/src/emui/EmuiAppContext.tsx b/enclave-manager/web/src/emui/EmuiAppContext.tsx index b05a493a6a..92c4073f34 100644 --- a/enclave-manager/web/src/emui/EmuiAppContext.tsx +++ b/enclave-manager/web/src/emui/EmuiAppContext.tsx @@ -325,16 +325,18 @@ export const useFullEnclaves = (): Result => { cachedStarlarkRunsByEnclave, ]); - if (enclaves.isErr) { - return enclaves.cast(); - } - - return Result.ok( - enclaves.value.map((enclave) => ({ - ...enclave, - services: servicesByEnclave[enclave.shortenedUuid], - filesAndArtifacts: filesAndArtifactsByEnclave[enclave.shortenedUuid], - starlarkRun: starlarkRunsByEnclave[enclave.shortenedUuid], - })), + const fullEnclaves = useMemo( + () => + enclaves.map((enclaves) => + enclaves.map((enclave) => ({ + ...enclave, + services: cachedServicesByEnclave[enclave.shortenedUuid], + filesAndArtifacts: cachedFilesAndArtifactsByEnclave[enclave.shortenedUuid], + starlarkRun: cachedStarlarkRunsByEnclave[enclave.shortenedUuid], + })), + ), + [enclaves, cachedServicesByEnclave, cachedStarlarkRunsByEnclave, cachedFilesAndArtifactsByEnclave], ); + + return fullEnclaves; }; diff --git a/enclave-manager/web/src/emui/enclaves/EnclaveList.tsx b/enclave-manager/web/src/emui/enclaves/EnclaveList.tsx index 4b3da105d2..2a74456fa3 100644 --- a/enclave-manager/web/src/emui/enclaves/EnclaveList.tsx +++ b/enclave-manager/web/src/emui/enclaves/EnclaveList.tsx @@ -1,5 +1,5 @@ import { Button, ButtonGroup, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react"; -import { useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { CreateEnclaveButton } from "../../components/enclaves/CreateEnclaveButton"; import { EnclavesTable } from "../../components/enclaves/tables/EnclavesTable"; import { DeleteEnclavesButton } from "../../components/enclaves/widgets/DeleteEnclavesButton"; @@ -12,6 +12,21 @@ export const EnclaveList = () => { const [selectedEnclaves, setSelectedEnclaves] = useState([]); + const enclavesKey = useMemo( + () => + enclaves.isErr + ? "error" + : enclaves.value + .map((enclave) => enclave.shortenedUuid) + .sort() + .join("|"), + [enclaves], + ); + + useEffect(() => { + setSelectedEnclaves([]); + }, [enclavesKey]); + return ( diff --git a/enclave-manager/web/src/emui/enclaves/EnclaveRoutes.tsx b/enclave-manager/web/src/emui/enclaves/EnclaveRoutes.tsx index 11927696a4..525fb596fa 100644 --- a/enclave-manager/web/src/emui/enclaves/EnclaveRoutes.tsx +++ b/enclave-manager/web/src/emui/enclaves/EnclaveRoutes.tsx @@ -1,4 +1,6 @@ +import { Icon } from "@chakra-ui/react"; import { ServiceInfo } from "enclave-manager-sdk/build/api_container_service_pb"; +import { FiPlus } from "react-icons/fi"; import { Params, RouteObject } from "react-router-dom"; import { KurtosisClient } from "../../client/enclaveManager/KurtosisClient"; import { RemoveFunctions } from "../../utils/types"; @@ -22,11 +24,25 @@ export const enclaveRoutes = (kurtosisClient: KurtosisClient): RouteObject[] => path: "/enclave/:enclaveUUID", id: "enclave", handle: { - crumb: async ({ enclaves }: RemoveFunctions, params: Params) => { - const enclave = enclaves.unwrapOr([]).find((enclave) => enclave.shortenedUuid === params.enclaveUUID); + crumb: async ({ enclaves: enclavesResult }: RemoveFunctions, params: Params) => { + const enclaves = enclavesResult.unwrapOr([]); + const enclave = enclaves.find((enclave) => enclave.shortenedUuid === params.enclaveUUID); return { name: enclave?.name || params.enclaveUUID, destination: `/enclave/${params.enclaveUUID}`, + alternatives: [ + ...enclaves + .filter((enclave) => enclave.shortenedUuid !== params.enclaveUUID) + .map((enclave) => ({ + name: enclave.name, + destination: `/enclave/${enclave.shortenedUuid}`, + })), + { + name: "New Enclave", + destination: `${window.location.href}/#create-enclave`, + icon: , + }, + ], }; }, }, @@ -35,16 +51,23 @@ export const enclaveRoutes = (kurtosisClient: KurtosisClient): RouteObject[] => path: "service/:serviceUUID", handle: { crumb: async ({ servicesByEnclave }: RemoveFunctions, params: Params) => { - const service = Object.values( + const services = Object.values( servicesByEnclave[params.enclaveUUID || ""]?.unwrapOr({ serviceInfo: {} as Record, }).serviceInfo || {}, - ).find((service) => service.shortenedUuid === params.serviceUUID); + ); + const service = services.find((service) => service.shortenedUuid === params.serviceUUID); const serviceName = service?.name || "Unknown"; return { name: serviceName, destination: `/enclave/${params.enclaveUUID}/service/${params.serviceUUID}`, + alternatives: services + .filter((service) => service.shortenedUuid !== params.serviceUUID) + .map((service) => ({ + name: service.name, + destination: `/enclave/${params.enclaveUUID}/service/${service.shortenedUuid}`, + })), }; }, }, diff --git a/enclave-manager/web/src/utils/index.ts b/enclave-manager/web/src/utils/index.ts index 3fafafd28d..ca43265939 100644 --- a/enclave-manager/web/src/utils/index.ts +++ b/enclave-manager/web/src/utils/index.ts @@ -90,3 +90,11 @@ export async function asyncResult( return Result.err(errorMessage || stringifyError(e)); } } + +export function wrapResult(c: () => T, errorMessage?: string): Result { + try { + return Result.ok(c()); + } catch (e: any) { + return Result.err(errorMessage || stringifyError(e)); + } +}
Name Port Public Port Application Protocol
{name} {privatePorts[name].number}/{transportProtocolToString(port.transportProtocol)} {port.number}{port.maybeApplicationProtocol || Unknown}{port.maybeApplicationProtocol || Undefined}