diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 517fad9f..8ee71450 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -521,7 +521,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho } // Handle top bar or something - const defaultTop = isCloud ? 0 : 6 + const defaultTop = isCloud ? 0 : 7 const loginTextBrowser = !isLoggedIn ?
@@ -813,7 +813,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho }} MenuProps={{ style: { - zIndex: 10002, + zIndex: 15000, }, }} style={{ @@ -823,6 +823,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho color: "white", height: 45, width: 85, + zIndex: 14999, }} value={userdata.active_org.id} @@ -887,7 +888,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
- {regiontag} {image} {data.name} + {isCloud?{regiontag}:null} {image} {data.name}
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index d17b7d1d..3b3bab17 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2538,6 +2538,8 @@ const AngularWorkflow = (defaultprops) => { const timeout = 60000 while (true) { + // Wait 1 second before next request just in case of timeouts + await new Promise(r => setTimeout(r, 1000)); await fetchWithTimeout(`${globalUrl}/api/v1/workflows/${workflowId}/stream`, { method: "GET", headers: { diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index c9ee901c..4fd0e6bf 100755 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -36,6 +36,7 @@ import { } from "@mui/material"; import { + AutoFixHigh as AutoFixHighIcon, LockOpen as LockOpenIcon, OpenInNew as OpenInNewIcon, Apps as AppsIcon, @@ -293,6 +294,8 @@ const Apps = (props) => { const [loadAppsModalOpen, setLoadAppsModalOpen] = React.useState(false); const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); const [openApiModal, setOpenApiModal] = React.useState(false); + const [generateAppModal, setGenerateAppModal] = React.useState(false); + const [openApiModalType, setOpenApiModalType] = React.useState(""); const [openApiError, setOpenApiError] = React.useState(""); const [field1, setField1] = React.useState(""); @@ -1333,11 +1336,79 @@ const Apps = (props) => {
) : null; + const AppCreateButton = (props) => { + const { text, func, icon } = props; + + const [hover, setHover] = React.useState(false); + + return ( + setHover(true)} + onMouseLeave={() => setHover(false)} + onClick={func} + style={{ + flex: 1, + padding: 15, + margin: 10, + backgroundColor: hover ? theme.palette.surfaceColor : "transparent", + border: hover ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", + cursor: hover ? "pointer" : "default", + textAlign: "center", + height: 150, + }} + > + {icon} + + {text} + + + ) + } + return (
-

App Creator

+

+ App Creator +

+
+ { + setOpenApiModal(true) + }} + icon={} + /> + { + setGenerateAppModal(true) + }} + icon={} + /> +
+ + + + {/* {
+ */}
@@ -1459,6 +1531,30 @@ const Apps = (props) => { //} }; + const uploadFileDocumentation = (e) => { + const isDropzone = e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0; + const files = isDropzone ? e.dataTransfer.files : e.target.files; + + const reader = new FileReader(); + + try { + reader.addEventListener("load", (e) => { + const content = e.target.result; + setOpenApiData(content); + setIsDropzone(isDropzone); + setOpenApiModal(true); + }); + } catch (e) { + console.log("Error in dropzone: ", e); + } + + try { + reader.readAsText(files[0]); + } catch (error) { + toast("Failed to read file"); + } + }; + const uploadFile = (e) => { const isDropzone = e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0; @@ -2248,6 +2344,58 @@ const Apps = (props) => { }); }; + const validateDocumentationUrl = () => { + setValidation(true); + + // curl https://doc-to-openapi-stbuwivzoq-nw.a.run.app/doc_to_openapi -d '{"url": "https://gitlab.com/rhab/PyOTRS/-/raw/main/pyotrs/lib.py?ref_type=heads"}' -H "Content-Type: application/json" + const urldata = { + "url": openApi, + } + + //fetch("http://localhost:8080/doc_to_openapi", { + fetch("https://doc-to-openapi-stbuwivzoq-nw.a.run.app/doc_to_openapi", { + method: "POST", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(urldata), + }) + .then((response) => { + setValidation(false); + if (response.status !== 200) { + toast("Error in generation: "+response.status); + setOpenApiError("Error in generation - bad status: "+response.status); + return response.text(); + } + + return response.json(); + }) + .then((responseJson) => { + // Check if openapi or swagger in string of the json + try { + const parsedtext = JSON.stringify(responseJson); + if (parsedtext.indexOf("openapi") === -1 && parsedtext.indexOf("swagger") === -1) { + setValidation(false); + setOpenApiError("Error in generation: "+parsedtext); + return; + } + } catch (e) { + setValidation(false); + setOpenApiError("Error in generation (2): "+e.toString()); + return; + } + + console.log("Validating response!"); + validateOpenApi(responseJson); + }) + .catch((error) => { + setValidation(false); + toast(error.toString()); + setOpenApiError(error.toString()); + }); + } + const validateRemote = () => { setValidation(true); @@ -2544,14 +2692,13 @@ const Apps = (props) => { ) : null; - const errorText = - openApiError.length > 0 ? ( -
Error: {openApiError}
- ) : null; - const modalView = openApiModal ? ( + const errorText = openApiError.length > 0 ? (
Error: {openApiError}
) : null; + + const generateAppView = generateAppModal ? ( { + setGenerateAppModal(false); setOpenApiModal(false); }} PaperProps={{ @@ -2566,7 +2713,124 @@ const Apps = (props) => {
- Create a new app + Generate an app based on documentation (beta) +
+
+ + + Paste in a URL, and we will make it into an app for you. This may take multiple minutes based on the size of the documentation. {isCloud ? "" : "Uses to Shuffle Cloud (https://shuffler.io) for processing."} + + 0} + color="primary" + onClick={() => { + setOpenApiError(""); + validateDocumentationUrl(); + }} + > + Validate + + ), + }} + onChange={(e) => { + setOpenApi(e.target.value); + }} + helperText={ + + Should be a documentation page containing an API. + + } + placeholder="API Documentation URL" + fullWidth + /> +

Or upload document with the content (coming soon)

+ + + {errorText} +
+ + {circularLoader} + + + +
+
+ ) : null + + + const modalView = openApiModal ? ( + { + setOpenApiModal(false); + setGenerateAppModal(false); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + + +
+ Create a new app from OpenAPI / Swagger
@@ -2671,6 +2935,7 @@ const Apps = (props) => {
{appView} {modalView} + {generateAppView} {appsModalLoad} {deleteModal}