Skip to content

Commit

Permalink
feat: add service details to EM UI (#1352)
Browse files Browse the repository at this point in the history
## Description:
Adding the service information to the EM UI, such as service status,
uuid, env vars etc.

## Is this change user facing?
YES

## References (if applicable):
<!-- Add relevant Github Issues, Discord threads, or other helpful
information. -->

---------

Co-authored-by: Laurent Luce <laurentluce49@yahoo.com>
  • Loading branch information
adschwartz and laurentluce committed Sep 20, 2023
1 parent 87c1e1f commit 2ccd98d
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 75 deletions.
4 changes: 1 addition & 3 deletions engine/frontend/src/api/container.js
Expand Up @@ -102,9 +102,7 @@ export const getEnclaveInformation = async (host, port, token, apiHost) => {
)

return {
name: data.serviceInfo[serviceName].name,
uuid: data.serviceInfo[serviceName].serviceUuid,
privateIpAddr: data.serviceInfo[serviceName].privateIpAddr,
...data.serviceInfo[serviceName],
ports: ports,
}
})
Expand Down
67 changes: 43 additions & 24 deletions engine/frontend/src/component/CodeEditor.js
Expand Up @@ -12,6 +12,12 @@ export const CodeEditor = (
defaultState = languages.includes("json") ? "{\n}" : "",
autoFormat = false,
lineNumbers = false,
id = 0,
showCopyButton = true,
showDownloadButton = true,
showFormatButton = true,
buttonSizes = "sm",
border = "1px"
) => {
// https://github.com/microsoft/monaco-editor/blob/main/webpack-plugin/README.md#options
const [value, setValue] = useState(defaultState)
Expand All @@ -29,7 +35,7 @@ export const CodeEditor = (
// TODO: Add a promise to getEditor()
const getEditor = () => {
if (!monacoRef.current) return null;
return monacoRef.current.editor.getEditors()[0];
return monacoRef.current.editor.getEditors()[id];
}

const isEditorReadOnly = () => {
Expand All @@ -51,7 +57,7 @@ export const CodeEditor = (

useEffect(() => {
if (getEditor()) {
console.log("Changing readOnly in monaco to:", readOnlySetting)
// console.log("Changing readOnly in monaco to:", readOnlySetting)
getEditor().updateOptions({
readOnly: readOnlySetting,
})
Expand All @@ -61,15 +67,15 @@ export const CodeEditor = (
useEffect(() => {
if (formatCode) {
if (isEditorReadOnly()) {
console.log("Cannot format with readonly=true, requesting to set readOnly=false")
// console.log("Cannot format with readonly=true, requesting to set readOnly=false")
setReadOnlySetting(false)
} else {
if (getEditor()) {
getEditor()
.getAction('editor.action.formatDocument')
.run()
.then(() => {
console.log(`Formatting finished running. Setting readonly=${originalReadOnlySetting.current}`)
// console.log(`Formatting finished running. Setting readonly=${originalReadOnlySetting.current}`)
setReadOnlySetting(originalReadOnlySetting.current)
setFormatCode(false)
});
Expand Down Expand Up @@ -165,7 +171,7 @@ export const CodeEditor = (

return (
<Box
border="1px"
border={border}
borderColor='gray.700'
borderRadius="7"
margin={"1px"}
Expand All @@ -180,7 +186,7 @@ export const CodeEditor = (
onChange={handleEditorChange}
// onValidate={handleEditorValidation}
options={{
automaticLayout: true,
automaticLayout: false, // if this is `true` a ResizeObserver is installed. This causes issues with us managing the container size outside.
selectOnLineNumbers: lineNumbers,
lineNumbers: lineNumbers,
languages: languages,
Expand All @@ -191,25 +197,38 @@ export const CodeEditor = (
scrollBeyondLastLine: false
}}
/>
<Button
margin={1}
onClick={contentClipboard.onCopy}
>
{contentClipboard.hasCopied ? "Copied!" : "Copy"}
</Button>
<Button
margin={1}
onClick={handleDownload}
>
Download
</Button>
<Button
margin={1}
onClick={handleCodeFormat}
isDisabled={isNotFormattable()}
<Box
marginTop={1}
>
Format
</Button>
{showCopyButton && (
<Button
margin={1}
onClick={contentClipboard.onCopy}
size={buttonSizes}
>
{contentClipboard.hasCopied ? "Copied!" : "Copy"}
</Button>
)}
{showDownloadButton && (
<Button
margin={1}
onClick={handleDownload}
size={buttonSizes}
>
Download
</Button>
)}
{showFormatButton && (
<Button
margin={1}
onClick={handleCodeFormat}
isDisabled={isNotFormattable()}
size={buttonSizes}
>
Format
</Button>
)}
</Box>
</Box>
)
}
2 changes: 0 additions & 2 deletions engine/frontend/src/component/Enclaves.js
Expand Up @@ -141,8 +141,6 @@ const Enclaves = ({enclaves, isLoading, handleDeleteClick}) => {
const [enclaveName, setEnclaveName] = useState("")
const { isOpen, onOpen, onClose } = useDisclosure()

console.log(enclaveName)

const handleCreateEnvClick = () => {
navigate("/catalog")
}
Expand Down
6 changes: 3 additions & 3 deletions engine/frontend/src/component/LogView.js
@@ -1,13 +1,13 @@
import Heading from "./Heading"

export const LogView = ({heading, logs, size="h-[70%]" }) => {
export const LogView = ({heading, logs, size="h[100%]" }) => {
return (
<div className={`flex-col flex ${size}`}>
<Heading content={heading} />
<div className="overflow-y-auto h-full">
<ul className="border border-gray-200 p-2">
{logs.map((log, index) => (
<li key={index} className="p-2 border-b border-gray-200 text-black">
<li key={index} className="p-2 border-b border-gray-200 text-white">
{log}
</li>
))}
Expand Down Expand Up @@ -35,4 +35,4 @@ export const LogView = ({heading, logs, size="h-[70%]" }) => {
)
}
</div>
</div> */}
</div> */}
4 changes: 3 additions & 1 deletion engine/frontend/src/component/PackageCatalogForm.js
Expand Up @@ -92,11 +92,12 @@ const KeyValueTable = (dataCallBack) => {
</HStack>
</Box>
)}
renderAdd={addItem => <Button margin={1} onClick={addItem}>Add item</Button>}
renderAdd={addItem => <Button size={"sm"} margin={1} onClick={addItem}>Add item</Button>}
// renderEmpty={() => <p></p>}
/>
<Button
margin={1}
size={"sm"}
onClick={clipboard.onCopy}
>
<Tooltip label="Copy as JSON">
Expand All @@ -106,6 +107,7 @@ const KeyValueTable = (dataCallBack) => {
</Button>
<Button
margin={1}
size={"sm"}
onClick={paste}
>
<Tooltip label='Paste as a JSON key value map, e.g. `{ "key_1": "value", "key_2": 1 }` '>
Expand Down
121 changes: 93 additions & 28 deletions engine/frontend/src/component/ServiceInfo.js
@@ -1,16 +1,19 @@
import Heading from "./Heading";
import {useEffect, useState} from "react";
import {useLocation, useNavigate, useParams} from "react-router-dom";
import {LogView} from "./LogView";
import LeftPanel from "./LeftPanel";
import RightPanel from "./RightPanel";
import {getServiceLogs} from "../api/enclave";
import {useAppContext} from "../context/AppState";
import {Badge, Box, GridItem, Table, TableContainer, Tbody, Td, Tr} from "@chakra-ui/react";
import {CodeEditor} from "./CodeEditor";
import {LogView} from "./LogView";

const renderServices = (services, handleClick) => {
return services.map(service => {
return (
<div className={`flex items-center justify-center h-14 text-base bg-[#24BA27]`} key={service.name} onClick={() => handleClick(service)}>
<div className={`flex items-center justify-center h-14 text-base bg-[#24BA27]`} key={service.name}
onClick={() => handleClick(service)}>
<div className='cursor-default text-lg text-white'> {service.name} </div>
</div>
)
Expand All @@ -31,7 +34,8 @@ const ServiceInfo = () => {
let stream;
const ctrl = new AbortController();
const fetchLogs = async () => {
stream = await getServiceLogs(ctrl, enclaveName, serviceUuid, appData.apiHost); try {
stream = await getServiceLogs(ctrl, enclaveName, serviceUuid, appData.apiHost);
try {
for await (const res of stream) {
const log = res["serviceLogsByServiceUuid"][serviceUuid]["line"][0]
setLogs(logs => [...logs, log])
Expand All @@ -52,6 +56,75 @@ const ServiceInfo = () => {
navigate(fullPath, {state: {services, selected: service}})
}

const func = () => {
}

const codeBox = (id, parameterName, data) => {
const serializedData = JSON.stringify(data, null, 2)
return (
<Box>
{
CodeEditor(
func,
true,
`${parameterName}.json`,
["json"],
250,
serializedData,
true,
false,
id,
true,
true,
false,
"xs",
"1px"
)
}
</Box>
);
}

const tableRow = (heading, data) => {
let displayData = ""
try {
displayData = data()
} catch (e) {
console.error("Error while processing row", e)
displayData = "Error while retrieving information"
}
return (
<Tr key={heading}>
<Td><p><b>{heading}</b></p></Td>
<Td>{displayData}</Td>
</Tr>
);
};

const selectedSerialized = selected; // JSON.parse(JSON.stringify(selected))
const statusBadge = (status) => {
console.log(`status=${status}`)
let color = ""
let text = ""
if(status === "RUNNING" || status === 1){
color="green"
text="RUNNING"
} else if (status === "STOPPED" || status === 0) {
color="red"
text="STOPPED"
} else if(status ==="UNKNOWN" || status === 2){
color = "yellow"
text="UNKNOWN"
} else {
return (
<Badge>N/A</Badge>
)
}
return (
<Badge colorScheme={color}>{text}</Badge>
)
}

return (
<div className="flex h-full">
<LeftPanel
Expand All @@ -61,32 +134,24 @@ const ServiceInfo = () => {
renderList={() => renderServices(services, handleServiceClick)}
/>
<div className="flex h-full w-[calc(100vw-39rem)] flex-col space-y-5">
<div className='flex flex-col h-full space-y-1 bg-white'>
<Heading content={`${enclaveName} ::${selected.name}`}/>
<div className="flex-1">
<div className="text-xl text-left h-fit mb-2 ml-5 text-black">
Ports
</div>
<div className="overflow-auto">
{
selected.ports.map(port => {
const urlWithApplicationString = `${port.applicationProtocol}://localhost:${port.publicPortNumber}`
const urlWithoutApplicationString = `localhost:${port.publicPortNumber}`
const url = port.applicationProtocol ? urlWithApplicationString : urlWithoutApplicationString
<div className='flex flex-col h-full space-y-1 bg-[#171923]'>
<Heading content={`${enclaveName} - ${selected.name}`}/>
<TableContainer>
<Table variant='simple' size='sm'>
<Tbody>
{tableRow("Name", () => selectedSerialized.name)}
{tableRow("UUID", () => <pre>{selectedSerialized.serviceUuid}</pre>)}
{tableRow("Status", () => statusBadge(selectedSerialized.serviceStatus))}
{tableRow("Image", () => selectedSerialized.container.imageName)}
{tableRow("Ports", () => codeBox(0, "ports", selectedSerialized.ports))}
{tableRow("ENTRYPOINT", () => codeBox(1, "entrypoint", selectedSerialized.container.entrypointArgs))}
{tableRow("CMD", () => codeBox(2, "cmd", selectedSerialized.container.cmdArgs))}
{tableRow("ENV", () => codeBox(3, "env", selectedSerialized.container.envVars))}
</Tbody>
</Table>
</TableContainer>

return (
<div className="h-fit flex flex-row space-x-10 ml-5 text-black">
<div> {port.portName}:</div>
<a href={url} rel="noreferrer" className="grow">
<u> {url} </u>
</a>
</div>
)
})
}
</div>
</div>
<LogView heading={`Service Logs`} logs={logs}/>
{/*<LogView heading={`Service Logs`} logs={logs}/>*/}
</div>
</div>
<RightPanel home={false} isServiceInfo={true} enclaveName={enclaveName}/>
Expand Down
12 changes: 6 additions & 6 deletions engine/server/webapp/asset-manifest.json
@@ -1,13 +1,13 @@
{
"files": {
"main.css": "./static/css/main.d554a53d.css",
"main.js": "./static/js/main.78b479f1.js",
"main.css": "./static/css/main.d32f0461.css",
"main.js": "./static/js/main.4c387d64.js",
"index.html": "./index.html",
"main.d554a53d.css.map": "./static/css/main.d554a53d.css.map",
"main.78b479f1.js.map": "./static/js/main.78b479f1.js.map"
"main.d32f0461.css.map": "./static/css/main.d32f0461.css.map",
"main.4c387d64.js.map": "./static/js/main.4c387d64.js.map"
},
"entrypoints": [
"static/css/main.d554a53d.css",
"static/js/main.78b479f1.js"
"static/css/main.d32f0461.css",
"static/js/main.4c387d64.js"
]
}
2 changes: 1 addition & 1 deletion engine/server/webapp/index.html
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>Kurtosis Enclave Manager</title><script defer="defer" src="./static/js/main.78b479f1.js"></script><link href="./static/css/main.d554a53d.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>Kurtosis Enclave Manager</title><script defer="defer" src="./static/js/main.4c387d64.js"></script><link href="./static/css/main.d32f0461.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

Large diffs are not rendered by default.

0 comments on commit 2ccd98d

Please sign in to comment.