Skip to content

Commit

Permalink
feat: support downloading artifacts in the new emui (#1720)
Browse files Browse the repository at this point in the history
## Description:
This PR adds support for downloading artifacts as TGZ's from the new
emui.

Additionally this PR:
* updates the copy on the files table to File Artifacts
* Makes files non clickable
* Fixing the scrolling behaviour over monaco editors.

## Is this change user facing?
YES

---------

Co-authored-by: Anders Schwartz <adschwartz@users.noreply.github.com>
Co-authored-by: Anders Schwartz <anders.schwartz@kurtosistech.com>
  • Loading branch information
3 people authored Nov 8, 2023
1 parent 3d1e142 commit fbfeaa3
Show file tree
Hide file tree
Showing 31 changed files with 374 additions and 233 deletions.
23 changes: 22 additions & 1 deletion enclave-manager/web/src/client/enclaveManager/KurtosisClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { PromiseClient } from "@connectrpc/connect";
import { RunStarlarkPackageArgs, ServiceInfo } from "enclave-manager-sdk/build/api_container_service_pb";
import {
FilesArtifactNameAndUuid,
RunStarlarkPackageArgs,
ServiceInfo,
} from "enclave-manager-sdk/build/api_container_service_pb";
import {
CreateEnclaveArgs,
DestroyEnclaveArgs,
Expand All @@ -14,6 +18,7 @@ import {
GetListFilesArtifactNamesAndUuidsRequest,
GetServicesRequest,
GetStarlarkRunRequest,
InspectFilesArtifactContentsRequest,
RunStarlarkPackageRequest,
} from "enclave-manager-sdk/build/kurtosis_enclave_manager_api_pb";
import { EnclaveFullInfo } from "../../emui/enclaves/types";
Expand Down Expand Up @@ -135,6 +140,22 @@ export abstract class KurtosisClient {
}, `KurtosisClient could not listFilesArtifactNamesAndUuids for ${enclave.name}`);
}

async inspectFilesArtifactContents(enclave: RemoveFunctions<EnclaveInfo>, file: FilesArtifactNameAndUuid) {
return await asyncResult(() => {
const apicInfo = enclave.apiContainerInfo;
assertDefined(
apicInfo,
`Cannot listFilesArtifactNamesAndUuids because the passed enclave '${enclave.name}' does not have apicInfo`,
);
const request = new InspectFilesArtifactContentsRequest({
apicIpAddress: apicInfo.bridgeIpAddress,
apicPort: apicInfo.grpcPortInsideEnclave,
fileNamesAndUuid: file,
});
return this.client.inspectFilesArtifactContents(request, this.getHeaderOptions());
}, `KurtosisClient could not inspectFilesArtifactContents for ${enclave.name}`);
}

async createEnclave(
enclaveName: string,
apiContainerLogLevel: string,
Expand Down
3 changes: 3 additions & 0 deletions enclave-manager/web/src/components/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export const CodeEditor = ({ text, onTextChange, showLineNumbers }: CodeEditorPr
selectionHighlight: !isReadOnly,
occurrencesHighlight: !isReadOnly,
overviewRulerLanes: isReadOnly ? 0 : 3,
scrollbar: {
alwaysConsumeMouseWheel: false,
},
}}
defaultLanguage={"json"}
theme={"vs-dark"}
Expand Down
42 changes: 32 additions & 10 deletions enclave-manager/web/src/components/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { Button, ButtonProps, useToast } from "@chakra-ui/react";
import { Button, ButtonProps, IconButton, IconButtonProps, useToast } from "@chakra-ui/react";
import { FiCopy } from "react-icons/fi";
import { isDefined } from "../utils";

type CopyButtonProps = ButtonProps & {
type CopyButtonProps<IsIconButton extends boolean> = (IsIconButton extends true ? IconButtonProps : ButtonProps) & {
valueToCopy?: (() => string) | string | null;
text?: string;
text?: IsIconButton extends true ? string : never;
isIconButton?: IsIconButton;
contentName: string;
};

export const CopyButton = ({ valueToCopy, text, contentName, ...buttonProps }: CopyButtonProps) => {
export const CopyButton = <IsIconButton extends boolean>({
valueToCopy,
text,
contentName,
isIconButton,
...buttonProps
}: CopyButtonProps<IsIconButton>) => {
const toast = useToast();

const handleCopyClick = () => {
Expand All @@ -22,13 +29,28 @@ export const CopyButton = ({ valueToCopy, text, contentName, ...buttonProps }: C
}
};

if (!isDefined(valueToCopy)) {
if (!isDefined(valueToCopy) && !isDefined(buttonProps.onClick)) {
return null;
}

return (
<Button leftIcon={<FiCopy />} size={"xs"} colorScheme={"darkBlue"} onClick={handleCopyClick} {...buttonProps}>
{text || "Copy"}
</Button>
);
if (isIconButton) {
return (
<IconButton
icon={<FiCopy />}
size={"xs"}
variant={"ghost"}
colorScheme={"darkBlue"}
onClick={handleCopyClick}
{...(buttonProps as IconButtonProps)}
>
{text || "Copy"}
</IconButton>
);
} else {
return (
<Button leftIcon={<FiCopy />} size={"xs"} colorScheme={"darkBlue"} onClick={handleCopyClick} {...buttonProps}>
{text || "Copy"}
</Button>
);
}
};
22 changes: 11 additions & 11 deletions enclave-manager/web/src/components/DataTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TriangleDownIcon, TriangleUpIcon } from "@chakra-ui/icons";
import { chakra, Table, Tbody, Td, Th, Thead, Tr } from "@chakra-ui/react";
import { Button, chakra, Table, Tbody, Td, Th, Thead, Tr } from "@chakra-ui/react";
import {
ColumnDef,
flexRender,
Expand Down Expand Up @@ -79,13 +79,18 @@ export function DataTable<Data extends object>({
isNumeric={meta?.isNumeric}
textAlign={!!meta?.centerAligned ? "center" : undefined}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getCanSort() && (
<Button variant={"sortableHeader"} size={"xs"}>
{flexRender(header.column.columnDef.header, header.getContext())}
</Button>
)}
{!header.column.getCanSort() && flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getIsSorted() && (
<chakra.span pl="4">
{header.column.getIsSorted() === "desc" ? (
<TriangleDownIcon aria-label="sorted descending" />
<TriangleDownIcon aria-label="sorted descending" color={"gray.400"} />
) : (
<TriangleUpIcon aria-label="sorted ascending" />
<TriangleUpIcon aria-label="sorted ascending" color={"gray.400"} />
)}
</chakra.span>
)}
Expand All @@ -97,16 +102,11 @@ export function DataTable<Data extends object>({
</Thead>
<Tbody>
{table.getRowModel().rows.map((row) => (
<Tr key={row.id} bg={row.getIsSelected() ? "kurtosisSelected.100" : ""}>
<Tr key={row.id} bg={row.getIsSelected() ? "gray.700" : ""}>
{row.getVisibleCells().map((cell) => {
const meta = cell.column.columnDef.meta;
return (
<Td
key={cell.id}
isNumeric={meta?.isNumeric}
textAlign={!!meta?.centerAligned ? "center" : undefined}
width={cell.column.getSize()}
>
<Td key={cell.id} isNumeric={meta?.isNumeric} textAlign={!!meta?.centerAligned ? "center" : undefined}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Td>
);
Expand Down
52 changes: 36 additions & 16 deletions enclave-manager/web/src/components/DownloadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
import { Button, ButtonProps } from "@chakra-ui/react";
import { Button, ButtonProps, IconButton, IconButtonProps } from "@chakra-ui/react";
import { FiDownload } from "react-icons/fi";
import { isDefined } from "../utils";
import { saveTextAsFile } from "../utils/download";

type DownloadButtonProps = ButtonProps & {
type DownloadButtonProps<IsIconButton extends boolean> = (IsIconButton extends true ? IconButtonProps : ButtonProps) & {
valueToDownload?: (() => string) | string | null;
fileName: string;
text?: string;
text?: IsIconButton extends true ? string : never;
isIconButton?: IsIconButton;
};

export const DownloadButton = ({ valueToDownload, text, fileName, ...buttonProps }: DownloadButtonProps) => {
export const DownloadButton = <IsIconButton extends boolean>({
valueToDownload,
text,
fileName,
isIconButton,
...buttonProps
}: DownloadButtonProps<IsIconButton>) => {
const handleDownloadClick = () => {
if (isDefined(valueToDownload)) {
const v = typeof valueToDownload === "string" ? valueToDownload : valueToDownload();
saveTextAsFile(v, fileName);
}
};

if (!isDefined(valueToDownload)) {
if (!isDefined(valueToDownload) && !isDefined(buttonProps.onClick)) {
return null;
}

return (
<Button
leftIcon={<FiDownload />}
size={"xs"}
colorScheme={"darkBlue"}
onClick={handleDownloadClick}
{...buttonProps}
>
{text || "Download"}
</Button>
);
if (isIconButton) {
return (
<IconButton
icon={<FiDownload />}
size={"xs"}
variant={"ghost"}
colorScheme={"darkBlue"}
onClick={handleDownloadClick}
{...(buttonProps as IconButtonProps)}
/>
);
} else {
return (
<Button
leftIcon={<FiDownload />}
size={"xs"}
colorScheme={"darkBlue"}
onClick={handleDownloadClick}
{...buttonProps}
>
{text || "Download"}
</Button>
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { KurtosisAlertModal } from "./KurtosisAlertModal";

type FeatureNotImplementedModalProps = {
featureName: string;
issueUrl: string;
message?: string;
isOpen: boolean;
onClose: () => void;
};

export const FeatureNotImplementedModal = ({
featureName,
issueUrl,
message,
isOpen,
onClose,
Expand All @@ -18,14 +20,15 @@ export const FeatureNotImplementedModal = ({
title={`${featureName} unavailable`}
isOpen={isOpen}
onClose={onClose}
confirmText={"Submit Request"}
confirmText={"Go to Issue"}
onConfirm={() => {
onClose();
window.open("https://github.com/kurtosis-tech/kurtosis/issues", "_blank");
window.open(issueUrl, "_blank");
}}
confirmButtonProps={{ colorScheme: "kurtosisGreen" }}
content={
message || `${featureName} is not currently available. Please open a feature request if you'd like to use this.`
message ||
`${featureName} is not currently available. Please comment/upvote the issue if you would like to use it.`
}
/>
);
Expand Down
16 changes: 10 additions & 6 deletions enclave-manager/web/src/components/KurtosisThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ const theme = extendTheme({
body: `'Inter', sans-serif`,
},
colors: {
kurtosisSelected: {
100: "#292929",
},
kurtosisGreen: {
100: "#005e11",
200: "#008c19",
Expand All @@ -48,6 +45,7 @@ const theme = extendTheme({
700: "#99f7aa",
},
darkBlue: {
200: "#516A77",
400: "#516A77",
},
gray: {
Expand Down Expand Up @@ -99,8 +97,7 @@ const theme = extendTheme({
},
variants: {
outline: (props: StyleFunctionProps) => ({
_hover: { bg: "initial", borderColor: `${props.colorScheme}.400` },
_active: { bg: "initial" },
_hover: { borderColor: `${props.colorScheme}.400` },
color: `${props.colorScheme}.400`,
borderColor: "gray.300",
}),
Expand Down Expand Up @@ -131,8 +128,15 @@ const theme = extendTheme({
})),
ghost: defineStyle((props) => ({
_hover: { bg: "gray.650" },
color: `gray.100`,
})),
sortableHeader: (props: StyleFunctionProps) => {
const ghost = theme.components.Button.variants!.ghost(props);
return {
...ghost,
color: "gray.100",
textTransform: "uppercase",
};
},
nav: {
_active: {
bg: "gray.600",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export const ConfigureEnclaveModal = ({
<Modal closeOnOverlayClick={false} isOpen={isOpen} onClose={handleClose} isCentered size={"5xl"}>
<ModalOverlay />
<ModalContent>
<ModalHeader textAlign={"center"}>Enclave Configuration</ModalHeader>
<ModalHeader textAlign={"center"}>{!isDefined(existingEnclave) && "New "}Enclave Configuration</ModalHeader>
<ModalCloseButton />
<EnclaveConfigurationForm
ref={formRef}
Expand All @@ -187,7 +187,7 @@ export const ConfigureEnclaveModal = ({
>
<ModalBody p={"0px"}>
<Flex fontSize={"sm"} justifyContent={"center"} alignItems={"center"} gap={"12px"} pb={"12px"}>
<Text>Deploying</Text>
<Text>Configuring</Text>
<EnclaveSourceButton source={kurtosisPackage.name} size={"sm"} variant={"outline"} color={"gray.100"} />
</Flex>
{isDefined(error) && <KurtosisAlert message={error} />}
Expand Down Expand Up @@ -220,7 +220,7 @@ export const ConfigureEnclaveModal = ({
Cancel
</Button>
<Button type={"submit"} isLoading={isLoading} colorScheme={"kurtosisGreen"}>
Run
{existingEnclave ? "Update" : "Run"}
</Button>
</Flex>
</ModalFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,17 @@ export const EnclavesTable = ({ enclavesData, selection, onSelectionChange }: En
}),
columnHelper.accessor("status", {
header: "Status",
cell: (statusCell) => <EnclaveStatus status={statusCell.getValue()} />,
cell: (statusCell) => <EnclaveStatus status={statusCell.getValue()} variant={"square"} />,
}),
columnHelper.accessor("created", {
header: "Created",
cell: (createdCell) => (
<Button size={"xs"} variant={"ghost"}>
<FormatDateTime dateTime={createdCell.getValue()} format={"relative"} />
</Button>
<FormatDateTime
fontSize={"xs"}
fontWeight={"semibold"}
dateTime={createdCell.getValue()}
format={"relative"}
/>
),
}),
columnHelper.accessor("source", {
Expand Down
Loading

0 comments on commit fbfeaa3

Please sign in to comment.