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

feat: manage parameters and URL #1689

Merged
merged 27 commits into from Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions enclave-manager/web/.env
@@ -1,4 +1,5 @@
REACT_APP_KURTOSIS_DEFAULT_HOST=localhost
REACT_APP_KURTOSIS_DEFAULT_PORT=8081
REACT_APP_KURTOSIS_DEFAULT_EM_API_PORT=8081

REACT_APP_KURTOSIS_CLOUD_URL=https://cloud.kurtosis.com:9770
REACT_APP_KURTOSIS_CLOUD_UI_URL=https://cloud.kurtosis.com
REACT_APP_KURTOSIS_PACKAGE_INDEXER_URL=https://cloud.kurtosis.com:9770
3 changes: 3 additions & 0 deletions enclave-manager/web/.prettierignore
@@ -1,3 +1,6 @@
Ignore generated code:
src/client/packageIndexer/api/*.ts

# Ignore artifacts:
build
coverage
2 changes: 1 addition & 1 deletion enclave-manager/web/.prettierrc
@@ -1,4 +1,4 @@
{
"printWidth": 120,
"plugins": ["prettier-plugin-organize-imports"]
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-curly"]
}
8 changes: 6 additions & 2 deletions enclave-manager/web/package.json
@@ -1,7 +1,8 @@
{
"name": "frontend",
"version": "0.2.0",
"name": "enclave-manager-ui",
"version": "0.1.0",
"private": true,
"homepage": ".",
"dependencies": {
"@chakra-ui/icons": "^2.1.1",
"@chakra-ui/react": "^2.8.1",
Expand Down Expand Up @@ -30,14 +31,17 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"prettier": "3.0.3",
"prettier-plugin-curly": "^0.1.3",
"prettier-plugin-organize-imports": "^3.2.3",
"typescript": "^4.4.2"
},
"scripts": {
"prebuild": "rm -rf ../../engine/server/webapp",
"clean": "rm -rf build",
"cleanInstall": "rm -rf node_modules; yarn install",
"start": "react-scripts start",
"build": "react-scripts build",
"postbuild": "cp -r build/ ../../engine/server/webapp",
"prettier": "prettier . --check",
"prettier:fix": "prettier . --write",
"test": "react-scripts test",
Expand Down
14 changes: 8 additions & 6 deletions enclave-manager/web/src/client/constants.ts
@@ -1,10 +1,12 @@
import { isDefined } from "../utils";

export const KURTOSIS_DEFAULT_HOST = process.env.REACT_APP_KURTOSIS_DEFAULT_HOST || "localhost";
export const KURTOSIS_DEFAULT_PORT = isDefined(process.env.REACT_APP_KURTOSIS_DEFAULT_PORT)
? parseInt(process.env.REACT_APP_KURTOSIS_DEFAULT_PORT)
export const KURTOSIS_EM_DEFAULT_HOST = process.env.REACT_APP_KURTOSIS_DEFAULT_HOST || "localhost";
export const KURTOSIS_DEFAULT_EM_API_PORT = isDefined(process.env.REACT_APP_KURTOSIS_DEFAULT_EM_API_PORT)
? parseInt(process.env.REACT_APP_KURTOSIS_DEFAULT_EM_API_PORT)
: 8081;
export const KURTOSIS_DEFAULT_URL =
process.env.REACT_APP_KURTOSIS_DEFAULT_URL || `http://${KURTOSIS_DEFAULT_HOST}:${KURTOSIS_DEFAULT_PORT}`;
export const KURTOSIS_EM_API_DEFAULT_URL =
process.env.REACT_APP_KURTOSIS_DEFAULT_URL || `http://${KURTOSIS_EM_DEFAULT_HOST}:${KURTOSIS_DEFAULT_EM_API_PORT}`;

export const KURTOSIS_CLOUD_URL = process.env.REACT_APP_KURTOSIS_CLOUD_URL || "https://cloud.kurtosis.com:9770";
export const KURTOSIS_PACKAGE_INDEXER_URL =
process.env.REACT_APP_KURTOSIS_PACKAGE_INDEXER_URL || "https://cloud.kurtosis.com:9770";
export const KURTOSIS_CLOUD_HOST = process.env.REACT_APP_KURTOSIS_CLOUD_UI_URL || "https://cloud.kurtosis.com";
@@ -1,19 +1,24 @@
import { createPromiseClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web";
import { KurtosisEnclaveManagerServer } from "enclave-manager-sdk/build/kurtosis_enclave_manager_api_connect";
import { KURTOSIS_DEFAULT_PORT } from "../constants";
import { KURTOSIS_CLOUD_HOST, KURTOSIS_DEFAULT_EM_API_PORT } from "../constants";
import { KurtosisClient } from "./KurtosisClient";

function constructGatewayURL(host: string): string {
return `https://cloud.kurtosis.com/gateway/ips/${host}/ports/${KURTOSIS_DEFAULT_PORT}`;
function constructGatewayURL(remoteHost: string): string {
return `${KURTOSIS_CLOUD_HOST}/gateway/ips/${remoteHost}/ports/${KURTOSIS_DEFAULT_EM_API_PORT}`;
}

export class AuthenticatedKurtosisClient extends KurtosisClient {
private token: string;
private readonly token: string;

constructor(host: string, token: string) {
constructor(gatewayHost: string, token: string, parentUrl: URL, childUrl: URL) {
super(
createPromiseClient(KurtosisEnclaveManagerServer, createConnectTransport({ baseUrl: constructGatewayURL(host) })),
createPromiseClient(
KurtosisEnclaveManagerServer,
createConnectTransport({ baseUrl: constructGatewayURL(gatewayHost) }),
),
parentUrl,
childUrl,
);
this.token = token;
}
Expand Down
68 changes: 61 additions & 7 deletions enclave-manager/web/src/client/enclaveManager/KurtosisClient.ts
@@ -1,11 +1,13 @@
import { PromiseClient } from "@connectrpc/connect";
import { RunStarlarkPackageArgs } from "enclave-manager-sdk/build/api_container_service_pb";
import { RunStarlarkPackageArgs, ServiceInfo } from "enclave-manager-sdk/build/api_container_service_pb";
import {
CreateEnclaveArgs,
DestroyEnclaveArgs,
EnclaveAPIContainerInfo,
EnclaveInfo,
EnclaveMode,
GetServiceLogsArgs,
LogLineFilter,
} from "enclave-manager-sdk/build/engine_service_pb";
import { KurtosisEnclaveManagerServer } from "enclave-manager-sdk/build/kurtosis_enclave_manager_api_connect";
import {
Expand All @@ -14,18 +16,49 @@ import {
GetStarlarkRunRequest,
RunStarlarkPackageRequest,
} from "enclave-manager-sdk/build/kurtosis_enclave_manager_api_pb";
import { assertDefined, asyncResult } from "../../utils";
import { assertDefined, asyncResult, isDefined } from "../../utils";
import { RemoveFunctions } from "../../utils/types";
import { EnclaveFullInfo } from "../../emui/enclaves/types";

export abstract class KurtosisClient {
protected client: PromiseClient<typeof KurtosisEnclaveManagerServer>;
protected readonly client: PromiseClient<typeof KurtosisEnclaveManagerServer>;

constructor(client: PromiseClient<typeof KurtosisEnclaveManagerServer>) {
/* Full URL of the browser containing the EM UI covering two use cases:
* In local-mode this is: http://localhost:9711, http://localhost:3000 (with `yarn start` / dev mode)
* In authenticated mode this is: https://cloud.kurtosis.com/enclave-manager (this data/url is provided as a search param when the code loads)
*
* This URL is primarily used to generate links to the EM UI (where the hostname is included).
* */
protected readonly parentUrl: URL;

/* Full URL of the EM UI, covering two use cases:
* In local-mode this is the same as the `parentUrl`
* In authenticated mode : https://cloud.kurtosis.com/enclave-manager/gateway/ips/1-2-3-4/ports/1234/?searchparams... (this data/url is provided as a search param when the code loads)
*
* This URL is primarily used to set the React router basename so that the router is able to ignore any leading subdirectories.
* */
protected readonly childUrl: URL;

constructor(client: PromiseClient<typeof KurtosisEnclaveManagerServer>, parentUrl: URL, childUrl: URL) {
this.client = client;
this.parentUrl = parentUrl;
this.childUrl = childUrl;
}

abstract getHeaderOptions(): { headers?: Headers };

getParentBasePathUrl() {
return `${this.parentUrl.origin}${this.parentUrl.pathname}`;
}

getChildPath() {
return this.childUrl.pathname;
}

getChildUrl() {
return this.childUrl;
}

async checkHealth() {
return asyncResult(this.client.check({}, this.getHeaderOptions()));
}
Expand All @@ -50,7 +83,28 @@ export abstract class KurtosisClient {
apicPort: apicInfo.grpcPortInsideEnclave,
});
return this.client.getServices(request, this.getHeaderOptions());
}, "KurtosisClient could not getServices");
}, `KurtosisClient could not getServices for ${enclave.name}`);
}

async getServiceLogs(
abortController: AbortController,
enclave: RemoveFunctions<EnclaveFullInfo>,
services: ServiceInfo[],
followLogs?: boolean,
numLogLines?: number,
returnAllLogs?: boolean,
conjunctiveFilters: LogLineFilter[] = [],
) {
// Not currently using asyncResult as the return type here is an asyncIterable
const request = new GetServiceLogsArgs({
enclaveIdentifier: enclave.name,
serviceUuidSet: services.reduce((acc, service) => ({ ...acc, [service.serviceUuid]: true }), {}),
followLogs: isDefined(followLogs) ? followLogs : true,
conjunctiveFilters: conjunctiveFilters,
numLogLines: isDefined(numLogLines) ? numLogLines : 1500,
returnAllLogs: !!returnAllLogs,
});
return this.client.getServiceLogs(request, { ...this.getHeaderOptions(), signal: abortController.signal });
}

async getStarlarkRun(enclave: RemoveFunctions<EnclaveInfo>) {
Expand All @@ -65,7 +119,7 @@ export abstract class KurtosisClient {
apicPort: apicInfo.grpcPortInsideEnclave,
});
return this.client.getStarlarkRun(request, this.getHeaderOptions());
}, "KurtosisClient could not getStarlarkRun");
}, `KurtosisClient could not getStarlarkRun for ${enclave.name}`);
}

async listFilesArtifactNamesAndUuids(enclave: RemoveFunctions<EnclaveInfo>) {
Expand All @@ -80,7 +134,7 @@ export abstract class KurtosisClient {
apicPort: apicInfo.grpcPortInsideEnclave,
});
return this.client.listFilesArtifactNamesAndUuids(request, this.getHeaderOptions());
}, "KurtosisClient could not listFilesArtifactNamesAndUuids");
}, `KurtosisClient could not listFilesArtifactNamesAndUuids for ${enclave.name}`);
}

async createEnclave(
Expand Down
Expand Up @@ -66,23 +66,37 @@ export const KurtosisClientProvider = ({ children }: PropsWithChildren) => {
useEffect(() => {
(async () => {
const searchParams = new URLSearchParams(window.location.search);
const requireAuth = isStringTrue(searchParams.get("require_authentication"));
const requestedApiHost = searchParams.get("api_host");
const requireAuth = isStringTrue(searchParams.get("require-authentication"));

try {
setError(undefined);
let newClient: KurtosisClient | null = null;

if (requireAuth) {
const requestedApiHost = searchParams.get("api-host");
assertDefined(requestedApiHost, `The parameter 'requestedApiHost' is not defined`);

// Get the parent location and path:
let parentLocationPath = paramToUrl(searchParams, "parent-location-path") || new URL(window.location.href);
// Get the child location and path:
let childLocationPath = paramToUrl(searchParams, "child-location-path") || new URL(window.location.href);

if (isDefined(jwtToken)) {
newClient = new AuthenticatedKurtosisClient(requestedApiHost, jwtToken);
newClient = new AuthenticatedKurtosisClient(
requestedApiHost,
jwtToken,
parentLocationPath,
childLocationPath,
);
}
} else {
newClient = new LocalKurtosisClient();
}

if (isDefined(newClient)) {
const checkResp = await newClient.checkHealth();
if (checkResp.isErr) {
setError("Cannot reach the enclave manager backend - is your enclave manager definitely running?");
setError("Cannot reach the enclave manager backend - is the Enclave Manager API running and accessible?");
return;
}
setClient(newClient);
Expand Down Expand Up @@ -124,3 +138,14 @@ export const useKurtosisClient = (): KurtosisClient => {

return client;
};

const paramToUrl = (searchParams: URLSearchParams, param: string) => {
let paramString = searchParams.get(param);
if (paramString === null) {
return null;
} else {
paramString = atob(paramString);
assertDefined(paramString, `The parameter ${param}' is not defined`);
return new URL(paramString);
}
};
@@ -1,12 +1,20 @@
import { createPromiseClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web";
import { KurtosisEnclaveManagerServer } from "enclave-manager-sdk/build/kurtosis_enclave_manager_api_connect";
import { KURTOSIS_DEFAULT_URL } from "../constants";
import { KURTOSIS_EM_API_DEFAULT_URL } from "../constants";
import { KurtosisClient } from "./KurtosisClient";

export class LocalKurtosisClient extends KurtosisClient {
constructor() {
super(createPromiseClient(KurtosisEnclaveManagerServer, createConnectTransport({ baseUrl: KURTOSIS_DEFAULT_URL })));
const defaultUrl = new URL(`${window.location.protocol}//${window.location.host}`);
super(
createPromiseClient(
KurtosisEnclaveManagerServer,
createConnectTransport({ baseUrl: KURTOSIS_EM_API_DEFAULT_URL }),
),
defaultUrl,
defaultUrl,
);
}

getHeaderOptions() {
Expand Down
@@ -1,15 +1,18 @@
import { createPromiseClient, PromiseClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web";
import { asyncResult } from "../../utils";
import { KURTOSIS_CLOUD_URL } from "../constants";
import { KURTOSIS_PACKAGE_INDEXER_URL } from "../constants";
import { KurtosisPackageIndexer } from "./api/kurtosis_package_indexer_connect";
import { ReadPackageRequest } from "./api/kurtosis_package_indexer_pb";

export class KurtosisPackageIndexerClient {
private client: PromiseClient<typeof KurtosisPackageIndexer>;

constructor() {
this.client = createPromiseClient(KurtosisPackageIndexer, createConnectTransport({ baseUrl: KURTOSIS_CLOUD_URL }));
this.client = createPromiseClient(
KurtosisPackageIndexer,
createConnectTransport({ baseUrl: KURTOSIS_PACKAGE_INDEXER_URL }),
);
}

getPackages = async () => {
Expand Down
5 changes: 3 additions & 2 deletions enclave-manager/web/src/components/CopyButton.tsx
Expand Up @@ -5,17 +5,18 @@ import { isDefined } from "../utils";
type CopyButtonProps = ButtonProps & {
valueToCopy?: (() => string) | string | null;
text?: string;
contentName: string;
};

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

const handleCopyClick = () => {
if (isDefined(valueToCopy)) {
const v = typeof valueToCopy === "string" ? valueToCopy : valueToCopy();
navigator.clipboard.writeText(v);
toast({
title: `Copied '${v}' to the clipboard`,
title: `Copied ${contentName} to the clipboard`,
status: `success`,
});
}
Expand Down
13 changes: 11 additions & 2 deletions enclave-manager/web/src/components/KurtosisBreadcrumbs.tsx
Expand Up @@ -12,18 +12,27 @@ export type KurtosisBreadcrumb = {
export const KurtosisBreadcrumbs = () => {
const matches = useMatches() as UIMatch<
object,
{ crumb?: (data: object, params: Params<string>) => KurtosisBreadcrumb | Promise<KurtosisBreadcrumb> }
{
crumb?: (
data: Record<string, object>,
params: Params<string>,
) => KurtosisBreadcrumb | Promise<KurtosisBreadcrumb>;
}
>[];

const [matchCrumbs, setMatchCrumbs] = useState<KurtosisBreadcrumb[]>([]);

useEffect(() => {
(async () => {
const allLoaderData = matches
.filter((match) => isDefined(match.data))
.reduce((acc, match) => ({ ...acc, [match.id]: match.data }), {});

setMatchCrumbs(
await Promise.all(
matches
.map((match) =>
isDefined(match.handle?.crumb) ? Promise.resolve(match.handle.crumb(match.data, match.params)) : null,
isDefined(match.handle?.crumb) ? Promise.resolve(match.handle.crumb(allLoaderData, match.params)) : null,
)
.filter(isDefined),
),
Expand Down
9 changes: 5 additions & 4 deletions enclave-manager/web/src/components/ValueCard.tsx
@@ -1,4 +1,4 @@
import { Card, Flex, Text, useToast } from "@chakra-ui/react";
import { Card, Flex, Text } from "@chakra-ui/react";
import { ReactElement } from "react";
import { isDefined } from "../utils";
import { CopyButton } from "./CopyButton";
Expand All @@ -11,16 +11,17 @@ type ValueCardProps = {
};

export const ValueCard = ({ title, value, copyEnabled, copyValue }: ValueCardProps) => {
const toast = useToast();

return (
<Card height={"100%"} display={"flex"} flexDirection={"column"} justifyContent={"space-between"} gap={"16px"}>
<Flex flexDirection={"row"} justifyContent={"space-between"} alignItems={"center"} width={"100%"}>
<Text fontSize={"sm"} fontWeight={"extrabold"} textTransform={"uppercase"} color={"gray.400"}>
{title}
</Text>
{copyEnabled && (
<CopyButton valueToCopy={isDefined(copyValue) ? copyValue : typeof value === "string" ? value : null} />
<CopyButton
valueToCopy={isDefined(copyValue) ? copyValue : typeof value === "string" ? value : null}
contentName={"url"}
/>
)}
</Flex>
<Text fontSize={"xl"}>{value}</Text>
Expand Down
3 changes: 3 additions & 0 deletions enclave-manager/web/src/components/constants.ts
@@ -0,0 +1,3 @@
export const KURTOSIS_PACKAGE_PARAMS_URL_ARG = "package-args";
export const KURTOSIS_PACKAGE_ID_URL_ARG = "package-id";
export const KURTOSIS_CREATE_ENCLAVE_URL_ARG = "create-enclave";