Skip to content

Commit

Permalink
feat: folks can now use frontend to view file artifacts and it's cont…
Browse files Browse the repository at this point in the history
…ent. (#967)

Once this PR is merged, users can use frontend to view contents of the
file artifacts. The content itself is not formatted at the moment but
things like yaml formatter and json formatter can be added later on.
  • Loading branch information
Peeeekay committed Jul 25, 2023
1 parent 81a21eb commit fc87c31
Show file tree
Hide file tree
Showing 29 changed files with 645 additions and 16 deletions.
59 changes: 57 additions & 2 deletions engine/frontend/src/api/container.js
@@ -1,5 +1,5 @@
import google_protobuf_empty_pb from 'google-protobuf/google/protobuf/empty_pb.js'
import {GetServicesArgs, RunStarlarkPackageArgs} from "kurtosis-sdk/build/core/kurtosis_core_rpc_api_bindings/api_container_service_pb";
import {GetServicesArgs, RunStarlarkPackageArgs, FilesArtifactNameAndUuid, InspectFilesArtifactContentsRequest} from "kurtosis-sdk/build/core/kurtosis_core_rpc_api_bindings/api_container_service_pb";
import {ApiContainerServicePromiseClient} from 'kurtosis-sdk/build/core/kurtosis_core_rpc_api_bindings/api_container_service_grpc_web_pb'

const TransportProtocolEnum = ["tcp", "sctp", "udp"];
Expand All @@ -21,6 +21,62 @@ const getDataFromApiContainer = async (request, process) => {
return process(data)
}

export const getFileArtifactInfo = async(url, fileArtifactName) => {
const containerClient = new ApiContainerServicePromiseClient(url);
const makeGetFileArtifactInfo = async () => {
try {
const fileArtifactArgs = new FilesArtifactNameAndUuid()
fileArtifactArgs.setFilename(fileArtifactName)
const inspectFileArtifactReq = new InspectFilesArtifactContentsRequest()
inspectFileArtifactReq.setFileNamesAndUuid(fileArtifactArgs)
const responseFromGrpc = await containerClient.inspectFilesArtifactContents(inspectFileArtifactReq, {})
return responseFromGrpc.toObject()
} catch (error) {
return {
files:[]
}
}
}

const processFileArtifact = (data) => {
let processed = {}
const recursive = (sub_dirs, index, processed) => {
if (sub_dirs.length > index) {
const subDir = sub_dirs[index]
if (!processed[subDir] && subDir !== "") {
processed[subDir] = {}
}
recursive(sub_dirs, index + 1, processed[subDir])
}
}

const add_file_recursive = (sub_dirs, index, processed, file) => {
const subDir = sub_dirs[index]
if (index === sub_dirs.length - 1) {
processed[subDir] = {...processed[subDir], ...file}
} else {
add_file_recursive(sub_dirs, index + 1, processed[subDir], file)
}
}

data.fileDescriptionsList.map(file => {
const splitted_path = file.path.split("/")
if (splitted_path[splitted_path.length - 1] === "") {
recursive(splitted_path, 0, processed)
} else {
add_file_recursive(splitted_path, 0, processed, file)
}
})

return {
files: processed
}
}

const response = await getDataFromApiContainer(makeGetFileArtifactInfo, processFileArtifact)
return response;
}

export const getEnclaveInformation = async (url) => {
if (url === "") {
return {
Expand All @@ -34,7 +90,6 @@ export const getEnclaveInformation = async (url) => {
try {
const serviceArgs = new GetServicesArgs();
const responseFromGrpc = await containerClient.getServices(serviceArgs, null)
console.log(responseFromGrpc.toObject())
return responseFromGrpc.toObject()
} catch (error) {
return {serviceInfoMap:[]}
Expand Down
19 changes: 13 additions & 6 deletions engine/frontend/src/component/EnclaveInfo.js
@@ -1,7 +1,7 @@
import Heading from "./Heading";
import { useEffect, useState } from "react";
import {useNavigate, useParams, useLocation} from "react-router-dom";
import {getEnclaveInformation} from "../api/container";
import {getEnclaveInformation, getFileArtifactInfo} from "../api/container";

import NoData from "./NoData";
import LeftPanel from "./LeftPanel";
Expand Down Expand Up @@ -40,7 +40,7 @@ const renderServices = (services, handleClick) => {
})
}

const renderFileArtifacts = (file_artifacts) => {
const renderFileArtifacts = (file_artifacts, handleFileArtifactClick) => {
if (file_artifacts.length === 0) {
return (
<NoData
Expand All @@ -53,7 +53,7 @@ const renderFileArtifacts = (file_artifacts) => {

return file_artifacts.map((file_artifact)=> {
return (
<div className="border-4 bg-slate-800 text-lg align-middle text-center h-16 p-3 text-green-600">
<div className="border-4 bg-slate-800 text-lg align-middle text-center h-16 p-3 text-green-600" onClick={() => handleFileArtifactClick(file_artifact.name, file_artifacts)}>
<div> {file_artifact.name} </div>
</div>
)
Expand Down Expand Up @@ -81,7 +81,6 @@ const EncalveInfo = ({enclaves}) => {
setFileArtifacts(artifacts)
}
setEnclaveInfoLoading(false)

}
fetch()
}, [name, enclaves])
Expand All @@ -94,8 +93,15 @@ const EncalveInfo = ({enclaves}) => {
navigate(`/enclaves/${enclaveName}`, {replace:true})
}

const handleFileArtifactClick = async (fileArtifactName, fileArtifacts) => {
console.log("Artifacts: ", fileArtifacts)
const selected = enclaves.filter(enclave => enclave.name === name);
if (selected.length > 0) {
navigate(`/enclaves/${selected[0].name}/files/${fileArtifactName}`, {state: {fileArtifacts}})
}
}

const EnclaveInfoCompoenent = ({services, fileArtifacts, handleServiceClick}) => (
const EnclaveInfoCompoenent = ({services, fileArtifacts, handleServiceClick, handleFileArtifactClick}) => (
<div className='flex flex-col h-[calc(100vh-3rem)] space-y-1 overflow-auto'>
<div className="flex flex-col h-1/2 min-h-1/2 border-8">
<Heading content={"Services"} size={"text-xl"} />
Expand All @@ -106,7 +112,7 @@ const EncalveInfo = ({enclaves}) => {
<div className="flex flex-col h-[46%] border-8">
<Heading content={"File Artifacts"} size={"text-xl"} padding={"p-1"}/>
<div className="overflow-auto space-y-2">
{renderFileArtifacts(fileArtifacts)}
{renderFileArtifacts(fileArtifacts, handleFileArtifactClick)}
</div>
</div>
</div>
Expand All @@ -130,6 +136,7 @@ const EncalveInfo = ({enclaves}) => {
services={services}
fileArtifacts={fileArtifacts}
handleServiceClick={handleServiceClick}
handleFileArtifactClick={handleFileArtifactClick}
/>
}
</div>
Expand Down
198 changes: 198 additions & 0 deletions engine/frontend/src/component/FileArtifactInfo.js
@@ -0,0 +1,198 @@
import Heading from "./Heading";
import { useEffect, useState } from "react";
import {useNavigate, useParams, useLocation} from "react-router-dom";
import {getFileArtifactInfo} from "../api/container";

import NoData from "./NoData";
import LeftPanel from "./LeftPanel";
import RightPanel from "./RightPanel";
import LoadingOverlay from "./LoadingOverflow";
import e from "cors";

const BreadCumbs = ({currentPath, handleOnClick, handleCleanButton}) => {
const total = currentPath.length;

const BreadCumb = ({text, last, color="text-slate-800", index, handleOnClick}) => {
return (
<div className={`${color} cursor-default font-bold`} onClick={()=>handleOnClick(index)}>
{text} {last ? "" : "/"}
</div>)
}

return (
<div className="flex flex-row py-2 px-5 flex-wrap">
{
currentPath.map((path, index) => (
<BreadCumb key={index} text={path} last={total-1 === index} index={index} handleOnClick={handleOnClick}/>
))
}
{
currentPath.length > 0 ?
<div className="mx-3" onClick={handleCleanButton}>
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path color="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</div> : null
}
</div>
)
}

const renderFileArtfiacts = (fileArtifacts, handleClick) => {
return fileArtifacts.map(fileArtifact => {
return (
<div className={`flex items-center justify-center h-14 text-base bg-green-700`} key={fileArtifact.name} onClick={()=>handleClick(fileArtifact.name)}>
<div className='cursor-default text-lg text-white'> {fileArtifact.name} </div>
</div>
)
})
}

const renderFiles = (files, handleFileClick) => {
if (files.length === 0) {
return (
<NoData
text={`No File Preview Available`}
size={`text-xl`}
color={`text-red-400`}
/>
)
}

return Object.keys(files).map((key)=> {
return (
<div className="border-4 bg-slate-800 text-lg align-middle text-center h-16 p-3 text-green-600" onClick={()=> handleFileClick(key, files[key])}>
<div> {key} </div>
</div>
)
})
}

const FileArtifactInfo = ({enclaves}) => {
const navigate = useNavigate();

const params = useParams();
const {name: enclaveName, fileArtifactName} = params;
const [fileInfoLoading, setFileInfoLoading] = useState(false);
const [currentFiles, setCurrentFiles] = useState({})
const [files, setFiles] = useState({})
const [currentPath, setCurrentPath] = useState([])
const [detailInfo, setDetailInfo] = useState({})
const {state} = useLocation();
const {fileArtifacts} = state;

useEffect(() => {
if (enclaves.length === 0) {
navigate(`/enclaves/${enclaveName}`)
} else {
setFileInfoLoading(true)
const fetch = async () => {
const selected = enclaves.filter(enclave => enclave.name === enclaveName);
if (selected.length > 0) {
const {files} = await getFileArtifactInfo(selected[0].apiClient, fileArtifactName);
setFiles(files)
setCurrentFiles(files)
setCurrentPath([])
setDetailInfo({})
}
setFileInfoLoading(false)
}
fetch()
}
}, [fileArtifactName])

const handleCleanButton = () => {
setCurrentPath([])
setDetailInfo({})
setCurrentFiles(files)
}

const handleFileClick = (key, file) => {
if (file.path) {
setDetailInfo(file)
let current = files
currentPath.map(path => {
current = current[path]
})
setCurrentPath(c => [...c, key])
} else {
let current = files
currentPath.map(path => {
current = current[path]
})
setCurrentPath(c => [...c, key])
setCurrentFiles(current[key])
setDetailInfo({})
}
}

const handleBreadCrumbClick = (index) => {
if (index == currentPath.length - 1) {
// do nothing
} else {
const newCurrentPath = currentPath.slice(0, index+1)
let current = files
newCurrentPath.map(path => {
current = current[path]
})
setCurrentFiles(current)
setCurrentPath(newCurrentPath)
setDetailInfo({})
}
}

const handleLeftPanelClick = (fileArtifactName) => {
navigate(`/enclaves/${enclaveName}/files/${fileArtifactName}`, {replace:true, state:{fileArtifacts}})
}

const FileInfoComponent = ({files, handleFileClick, detailInfo}) => (
<div className='flex flex-col h-[90%] space-y-1 overflow-auto'>
{
(Object.keys(detailInfo).length !== 0) ?
<div className="flex h-3/4 flex-col">
<p className="text-lg font-bold text-right"> Size: {detailInfo.size}B </p>
<p className="break-all overflow-y-auto"> {detailInfo.textPreview.length > 0 ? detailInfo.textPreview : <h2 className="text-2xl text-center mt-20 text-red-800 font-bold">No Preview Available</h2>} </p>
</div> :
<div className="flex flex-col h-[85%] min-h-[85%] border-8">
<Heading content={"Files"} size={"text-xl"} />
<div className="overflow-auto space-y-2">
{renderFiles(files, handleFileClick, detailInfo)}
</div>
</div>
}
</div>
)

return (
<div className="flex h-full">
<LeftPanel
home={false}
heading={"File Artifacts"}
renderList={ ()=> renderFileArtfiacts(fileArtifacts, handleLeftPanelClick)}
/>

<div className="flex bg-white w-[calc(100vw-39rem)] flex-col space-y-5">
<div className="h-[3rem] flex items-center justify-center m-2">
<Heading content={`${enclaveName}::${fileArtifactName}`} />
</div>
<BreadCumbs
currentPath={currentPath}
handleOnClick={handleBreadCrumbClick}
handleCleanButton={handleCleanButton}
/>
{fileInfoLoading ?
<LoadingOverlay /> :
<FileInfoComponent
files={currentFiles}
handleFileClick={handleFileClick}
detailInfo={detailInfo}
/>
}
</div>

<RightPanel isServiceInfo={true} enclaveName={enclaveName}/>
</div>
)
}

export default FileArtifactInfo;
4 changes: 3 additions & 1 deletion engine/frontend/src/component/Home.js
Expand Up @@ -3,8 +3,9 @@ import TitleBar from "./TitleBar"
import Main from "./Main"
import EnclaveInfo from "./EnclaveInfo";
import ServiceInfo from "./ServiceInfo";
import CreateEnclave from './CreateEnclave';
import FileArtifactInfo from './FileArtifactInfo';
import Enclaves from "./Enclaves";
import CreateEnclave from "./CreateEnclave"
import { useEffect, useState } from "react";
import {getEnclavesFromKurtosis} from "../api/enclave";

Expand Down Expand Up @@ -38,6 +39,7 @@ const Home = () => {
<Route exact path="/enclaves" element={<Enclaves enclaves={enclaves} isLoading={encalveLoading}/>} />
<Route path="/enclaves/:name" element={<EnclaveInfo enclaves={enclaves}/>} />
<Route path="/enclaves/:name/services/:uuid" element={<ServiceInfo/>} />
<Route path="/enclaves/:name/files/:fileArtifactName" element={<FileArtifactInfo enclaves={enclaves}/>} />
</Routes>
</div>
</div>
Expand Down
12 changes: 6 additions & 6 deletions engine/server/webapp/asset-manifest.json
@@ -1,13 +1,13 @@
{
"files": {
"main.css": "/static/css/main.a48af3f9.css",
"main.js": "/static/js/main.4ec82f07.js",
"main.css": "/static/css/main.25a20be6.css",
"main.js": "/static/js/main.946a4196.js",
"index.html": "/index.html",
"main.a48af3f9.css.map": "/static/css/main.a48af3f9.css.map",
"main.4ec82f07.js.map": "/static/js/main.4ec82f07.js.map"
"main.25a20be6.css.map": "/static/css/main.25a20be6.css.map",
"main.946a4196.js.map": "/static/js/main.946a4196.js.map"
},
"entrypoints": [
"static/css/main.a48af3f9.css",
"static/js/main.4ec82f07.js"
"static/css/main.25a20be6.css",
"static/js/main.946a4196.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 Frontend</title><script defer="defer" src="/static/js/main.4ec82f07.js"></script><link href="/static/css/main.a48af3f9.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 Frontend</title><script defer="defer" src="/static/js/main.946a4196.js"></script><link href="/static/css/main.25a20be6.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

0 comments on commit fc87c31

Please sign in to comment.