Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: enclave ui feedback implementation #1725

Merged
merged 9 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
14 changes: 9 additions & 5 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 Down Expand Up @@ -100,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 @@ -133,6 +129,14 @@ const theme = extendTheme({
ghost: defineStyle((props) => ({
_hover: { bg: "gray.650" },
})),
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
64 changes: 34 additions & 30 deletions enclave-manager/web/src/components/enclaves/tables/PortsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,71 @@
import { ExternalLinkIcon } from "@chakra-ui/icons";
import { Flex, Link, Text } from "@chakra-ui/react";
import { Flex, Text } from "@chakra-ui/react";
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
import { Port } from "enclave-manager-sdk/build/api_container_service_pb";
import { useMemo } from "react";
import { CopyButton } from "../../CopyButton";
import { DataTable } from "../../DataTable";
import { transportProtocolToString } from "../utils";

const columnHelper = createColumnHelper<Port>();
type PortsTableRow = {
port: { transportProtocol: string; privatePort: number; name: string };
link: string;
};

const getPortTableRows = (privatePorts: Record<string, Port>, publicPorts: Record<string, Port>): PortsTableRow[] => {
return Object.entries(privatePorts).map(([name, port]) => ({
port: { transportProtocol: transportProtocolToString(port.transportProtocol), privatePort: port.number, name },
link: "Coming soon",
}));
};

const columnHelper = createColumnHelper<PortsTableRow>();

type PortsTableProps = {
ports: Port[];
ip: string;
isPublic?: boolean;
privatePorts: Record<string, Port>;
publicPorts: Record<string, Port>;
};

export const PortsTable = ({ ports, ip, isPublic }: PortsTableProps) => {
const columns = useMemo<ColumnDef<Port, any>[]>(
export const PortsTable = ({ privatePorts, publicPorts }: PortsTableProps) => {
const columns = useMemo<ColumnDef<PortsTableRow, any>[]>(
() => [
columnHelper.accessor("number", {
columnHelper.accessor("port", {
header: "Port",
cell: ({ row, getValue }) => (
<Flex flexDirection={"column"} gap={"10px"}>
<Text>{row.original.maybeApplicationProtocol || "Unknown protocol"}</Text>
<Text>{row.original.port.name || "Unknown protocol"}</Text>
<Text fontSize={"xs"} color={"gray.400"} fontWeight={"semibold"}>
{row.original.number}/{transportProtocolToString(row.original.transportProtocol)}
{row.original.port.privatePort}/{row.original.port.transportProtocol}
</Text>
</Flex>
),
}),
columnHelper.accessor("maybeApplicationProtocol", {
columnHelper.accessor("link", {
header: "Link",
minSize: 800,
cell: ({ row }) => (
<Text width={"100%"}>
{isPublic && (
<Link
href={`${row.original.maybeApplicationProtocol}://${ip}:${row.original.number}`}
target="_blank"
rel="noopener noreferrer"
isExternal
>
{row.original.maybeApplicationProtocol}://{ip}:{row.original.number} <ExternalLinkIcon mx="2px" />
</Link>
)}
{!isPublic && `${row.original.maybeApplicationProtocol}://${ip}:${row.original.number}`}
</Text>
),
cell: ({ row }) => <Text width={"100%"}>{row.original.link}</Text>,
}),
columnHelper.display({
id: "copyButton",
cell: ({ row }) => (
<Flex justifyContent={"flex-end"}>
<CopyButton
contentName={"link"}
valueToCopy={`${row.original.maybeApplicationProtocol}://${ip}:${row.original.number}`}
isIconButton
aria-label={"Copy this port"}
valueToCopy={`${row.original.port.privatePort}`}
/>
</Flex>
),
}),
],
[ip, isPublic],
[],
);

return <DataTable columns={columns} data={ports} defaultSorting={[{ id: "number", desc: true }]} />;
return (
<DataTable
columns={columns}
data={getPortTableRows(privatePorts, publicPorts)}
defaultSorting={[{ id: "number", desc: true }]}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type ServicesTableRow = {
status: ServiceStatus;
// started: DateTime | null; TODO: The api needs to support this field
image?: string;
ports: { privatePorts: Port[]; publicPorts: Port[] };
ports: { privatePorts: Record<string, Port>; publicPorts: Record<string, Port> };
};

const serviceToRow = (service: ServiceInfo): ServicesTableRow => {
Expand All @@ -30,8 +30,8 @@ const serviceToRow = (service: ServiceInfo): ServicesTableRow => {
status: service.serviceStatus,
image: service.container?.imageName,
ports: {
privatePorts: Object.values(service.privatePorts),
publicPorts: Object.values(service.maybePublicPorts),
privatePorts: service.privatePorts,
publicPorts: service.maybePublicPorts,
},
};
};
Expand Down Expand Up @@ -60,7 +60,7 @@ export const ServicesTable = ({ enclaveShortUUID, servicesResponse }: ServicesTa
}),
columnHelper.accessor("status", {
header: "Status",
cell: (statusCell) => <ServiceStatusTag status={statusCell.getValue()} />,
cell: (statusCell) => <ServiceStatusTag status={statusCell.getValue()} variant={"square"} />,
}),
columnHelper.accessor("image", {
header: "Image",
Expand All @@ -75,10 +75,7 @@ export const ServicesTable = ({ enclaveShortUUID, servicesResponse }: ServicesTa
/>
),
sortingFn: (a, b) =>
a.original.ports.publicPorts.length +
a.original.ports.privatePorts.length -
b.original.ports.publicPorts.length -
b.original.ports.privatePorts.length,
Object.keys(a.original.ports.publicPorts).length - Object.keys(b.original.ports.publicPorts).length,
}),
columnHelper.accessor("serviceUUID", {
header: "Logs",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Tag } from "@chakra-ui/react";
import { Tag, Text } from "@chakra-ui/react";
import { FilesArtifactNameAndUuid } from "enclave-manager-sdk/build/api_container_service_pb";
import { isDefined } from "../../../utils";

Expand All @@ -12,8 +12,8 @@ export const EnclaveArtifactsSummary = ({ artifacts }: EnclaveArtifactsSummaryPr
}

return (
<Button variant={"ghost"} size={"xs"}>
<Text fontWeight={"semibold"} fontSize={"xs"}>
{artifacts.length}
</Button>
</Text>
);
};
Loading