Added a UI for uploading docs and getting an OpenAPI output
This commit is contained in:
@@ -521,7 +521,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle top bar or something
|
// Handle top bar or something
|
||||||
const defaultTop = isCloud ? 0 : 6
|
const defaultTop = isCloud ? 0 : 7
|
||||||
const loginTextBrowser = !isLoggedIn ?
|
const loginTextBrowser = !isLoggedIn ?
|
||||||
<div style={{display: "flex", minWidth: 1250, maxWidth: 1250, margin: "auto", textAlign: "center",}}>
|
<div style={{display: "flex", minWidth: 1250, maxWidth: 1250, margin: "auto", textAlign: "center",}}>
|
||||||
<div style={{display: "flex", flex: 1, }}>
|
<div style={{display: "flex", flex: 1, }}>
|
||||||
@@ -813,7 +813,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
|
|||||||
}}
|
}}
|
||||||
MenuProps={{
|
MenuProps={{
|
||||||
style: {
|
style: {
|
||||||
zIndex: 10002,
|
zIndex: 15000,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
@@ -823,6 +823,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
|
|||||||
color: "white",
|
color: "white",
|
||||||
height: 45,
|
height: 45,
|
||||||
width: 85,
|
width: 85,
|
||||||
|
zIndex: 14999,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
value={userdata.active_org.id}
|
value={userdata.active_org.id}
|
||||||
@@ -887,7 +888,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
|
|||||||
|
|
||||||
<Tooltip color="primary" title={parsedTitle} placement="left">
|
<Tooltip color="primary" title={parsedTitle} placement="left">
|
||||||
<div style={{display: "flex"}}>
|
<div style={{display: "flex"}}>
|
||||||
<Typography variant="body2" style={{borderRadius: theme.palette.borderRadius, float: "left", margin: "0 0 0 0", marginRight: 25, }}>{regiontag}</Typography> {image} <span style={{marginLeft: 8}}>{data.name}</span>
|
{isCloud?<Typography variant="body2" style={{borderRadius: theme.palette.borderRadius, float: "left", margin: "0 0 0 0", marginRight: 25, }}>{regiontag}</Typography>:null} {image} <span style={{marginLeft: 8}}>{data.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@@ -2538,6 +2538,8 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
const timeout = 60000
|
const timeout = 60000
|
||||||
|
|
||||||
while (true) {
|
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`, {
|
await fetchWithTimeout(`${globalUrl}/api/v1/workflows/${workflowId}/stream`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
+273
-8
@@ -36,6 +36,7 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AutoFixHigh as AutoFixHighIcon,
|
||||||
LockOpen as LockOpenIcon,
|
LockOpen as LockOpenIcon,
|
||||||
OpenInNew as OpenInNewIcon,
|
OpenInNew as OpenInNewIcon,
|
||||||
Apps as AppsIcon,
|
Apps as AppsIcon,
|
||||||
@@ -293,6 +294,8 @@ const Apps = (props) => {
|
|||||||
const [loadAppsModalOpen, setLoadAppsModalOpen] = React.useState(false);
|
const [loadAppsModalOpen, setLoadAppsModalOpen] = React.useState(false);
|
||||||
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
|
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
|
||||||
const [openApiModal, setOpenApiModal] = React.useState(false);
|
const [openApiModal, setOpenApiModal] = React.useState(false);
|
||||||
|
const [generateAppModal, setGenerateAppModal] = React.useState(false);
|
||||||
|
|
||||||
const [openApiModalType, setOpenApiModalType] = React.useState("");
|
const [openApiModalType, setOpenApiModalType] = React.useState("");
|
||||||
const [openApiError, setOpenApiError] = React.useState("");
|
const [openApiError, setOpenApiError] = React.useState("");
|
||||||
const [field1, setField1] = React.useState("");
|
const [field1, setField1] = React.useState("");
|
||||||
@@ -1333,11 +1336,79 @@ const Apps = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
const AppCreateButton = (props) => {
|
||||||
|
const { text, func, icon } = props;
|
||||||
|
|
||||||
|
const [hover, setHover] = React.useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
onMouseEnter={() => 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}
|
||||||
|
<Typography>
|
||||||
|
{text}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{}}>
|
<div style={{}}>
|
||||||
<Paper square style={uploadViewPaperStyle}>
|
<Paper square style={uploadViewPaperStyle}>
|
||||||
<div style={{ margin: 25 }}>
|
<div style={{ margin: 25 }}>
|
||||||
<h2>App Creator</h2>
|
<h2 style={{
|
||||||
|
textAlign: "center",
|
||||||
|
}}>
|
||||||
|
App Creator
|
||||||
|
</h2>
|
||||||
|
<div style={{display: "flex"}}>
|
||||||
|
<AppCreateButton
|
||||||
|
text="Generate from OpenAPI/Swagger"
|
||||||
|
func={() => {
|
||||||
|
setOpenApiModal(true)
|
||||||
|
}}
|
||||||
|
icon={<PublishIcon style={{minHeight: 50, maxHeigth: 50, }} />}
|
||||||
|
/>
|
||||||
|
<AppCreateButton
|
||||||
|
text="Generate from Documentation"
|
||||||
|
func={() => {
|
||||||
|
setGenerateAppModal(true)
|
||||||
|
}}
|
||||||
|
icon={<AutoFixHighIcon style={{minHeight: 50, maxHeigth: 50, }} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
to="/apps/new"
|
||||||
|
style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
textDecoration: "none",
|
||||||
|
color: "#f85a3e",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
component="label"
|
||||||
|
color="secondary"
|
||||||
|
style={{}}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
Create from scratch
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
{/*
|
||||||
<a
|
<a
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
href="https://shuffler.io/docs/apps"
|
href="https://shuffler.io/docs/apps"
|
||||||
@@ -1420,6 +1491,7 @@ const Apps = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Paper square style={uploadViewPaperStyle}>
|
<Paper square style={uploadViewPaperStyle}>
|
||||||
@@ -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 uploadFile = (e) => {
|
||||||
const isDropzone =
|
const isDropzone =
|
||||||
e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0;
|
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 = () => {
|
const validateRemote = () => {
|
||||||
setValidation(true);
|
setValidation(true);
|
||||||
|
|
||||||
@@ -2544,14 +2692,13 @@ const Apps = (props) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
const errorText =
|
const errorText = openApiError.length > 0 ? ( <div style={{ marginTop: 10 }}>Error: {openApiError}</div>) : null;
|
||||||
openApiError.length > 0 ? (
|
|
||||||
<div style={{ marginTop: 10 }}>Error: {openApiError}</div>
|
const generateAppView = generateAppModal ? (
|
||||||
) : null;
|
|
||||||
const modalView = openApiModal ? (
|
|
||||||
<Dialog
|
<Dialog
|
||||||
open={openApiModal}
|
open={generateAppModal}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
|
setGenerateAppModal(false);
|
||||||
setOpenApiModal(false);
|
setOpenApiModal(false);
|
||||||
}}
|
}}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
@@ -2566,7 +2713,124 @@ const Apps = (props) => {
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<div style={{ color: "rgba(255,255,255,0.9)" }}>
|
<div style={{ color: "rgba(255,255,255,0.9)" }}>
|
||||||
Create a new app
|
Generate an app based on documentation (beta)
|
||||||
|
</div>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
|
||||||
|
<Typography variant="body1">
|
||||||
|
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."}
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
style={{ backgroundColor: inputColor }}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
color: "white",
|
||||||
|
height: "50px",
|
||||||
|
fontSize: "1em",
|
||||||
|
},
|
||||||
|
endAdornment: (
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
borderRadius: "0px",
|
||||||
|
marginTop: "0px",
|
||||||
|
height: "50px",
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
disabled={openApi.length === 0 || appValidation.length > 0}
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenApiError("");
|
||||||
|
validateDocumentationUrl();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Validate
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
setOpenApi(e.target.value);
|
||||||
|
}}
|
||||||
|
helperText={
|
||||||
|
<span style={{ color: "white", marginBottom: "2px" }}>
|
||||||
|
Should be a documentation page containing an API.
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
placeholder="API Documentation URL"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<p>Or upload document with the content (coming soon)</p>
|
||||||
|
<input
|
||||||
|
hidden
|
||||||
|
type="file"
|
||||||
|
ref={upload}
|
||||||
|
multiple={false}
|
||||||
|
onChange={uploadFileDocumentation}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled
|
||||||
|
onClick={() => upload.current.click()}
|
||||||
|
>
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
{errorText}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
{circularLoader}
|
||||||
|
<Button
|
||||||
|
style={{ borderRadius: "0px" }}
|
||||||
|
onClick={() => {
|
||||||
|
setGenerateAppModal(false);
|
||||||
|
setOpenApiModal(false);
|
||||||
|
setAppValidation("");
|
||||||
|
setOpenApiError("");
|
||||||
|
setOpenApi("");
|
||||||
|
setOpenApiData("");
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
style={{ borderRadius: "0px" }}
|
||||||
|
disabled={appValidation.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
redirectOpenApi();
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</FormControl>
|
||||||
|
</Dialog>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
|
||||||
|
const modalView = openApiModal ? (
|
||||||
|
<Dialog
|
||||||
|
open={openApiModal}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenApiModal(false);
|
||||||
|
setGenerateAppModal(false);
|
||||||
|
}}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: surfaceColor,
|
||||||
|
color: "white",
|
||||||
|
minWidth: "800px",
|
||||||
|
minHeight: "320px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<DialogTitle>
|
||||||
|
<div style={{ color: "rgba(255,255,255,0.9)" }}>
|
||||||
|
Create a new app from OpenAPI / Swagger
|
||||||
</div>
|
</div>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
|
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
|
||||||
@@ -2671,6 +2935,7 @@ const Apps = (props) => {
|
|||||||
<div>
|
<div>
|
||||||
{appView}
|
{appView}
|
||||||
{modalView}
|
{modalView}
|
||||||
|
{generateAppView}
|
||||||
{appsModalLoad}
|
{appsModalLoad}
|
||||||
{deleteModal}
|
{deleteModal}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user