import React, { useEffect} from 'react'; import { useInterval } from 'react-powerhooks'; import Grid from '@material-ui/core/Grid'; import Paper from '@material-ui/core/Paper'; import Divider from '@material-ui/core/Divider'; import ButtonBase from '@material-ui/core/ButtonBase'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; import FormControl from '@material-ui/core/FormControl'; import Tooltip from '@material-ui/core/Tooltip'; import YAML from 'yaml' import {Link} from 'react-router-dom'; import CloudDownload from '@material-ui/icons/CloudDownload'; import { useAlert } from "react-alert"; import Dialog from '@material-ui/core/Dialog'; import DialogTitle from '@material-ui/core/DialogTitle'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import CircularProgress from '@material-ui/core/CircularProgress'; const surfaceColor = "#27292D" const inputColor = "#383B40" const Apps = (props) => { const { globalUrl, isLoggedIn, isLoaded } = props; //const [workflows, setWorkflows] = React.useState([]); const alert = useAlert() const [selectedApp, setSelectedApp] = React.useState({}); const [firstrequest, setFirstrequest] = React.useState(true) const [apps, setApps] = React.useState([]) const [filteredApps, setFilteredApps] = React.useState([]) const [validation, setValidation] = React.useState(false) const [isLoading, setIsLoading] = React.useState(false) const [openApi, setOpenApi] = React.useState("") const [openApiData, setOpenApiData] = React.useState("") const [appValidation, setAppValidation] = React.useState("") const [openApiModal, setOpenApiModal] = React.useState(false); const [openApiModalType, setOpenApiModalType] = React.useState(""); const [openApiError, setOpenApiError] = React.useState("") const { start, stop } = useInterval({ duration: 5000, startImmediate: false, callback: () => { getApps() } }); useEffect(() => { if (apps.length <= 0 && firstrequest) { document.title = "Shuffle - Apps" setFirstrequest(false) getApps() } }) const appViewStyle = { color: "#ffffff", width: "100%", display: "flex", } const paperAppStyle = { minHeight: 130, maxHeight: 130, minWidth: "100%", maxWidth: "100%", color: "white", backgroundColor: surfaceColor, cursor: "pointer", display: "flex", } const getApps = () => { fetch(globalUrl+"/api/v1/workflows/apps", { method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!") } return response.json() }) .then((responseJson) => { setApps(responseJson) setFilteredApps(responseJson) if (responseJson.length > 0) { setSelectedApp(responseJson[0]) } }) .catch(error => { alert.error(error.toString()) }); } const downloadApp = (inputdata) => { const id = inputdata.id alert.info("Preparing download.") fetch(globalUrl+"/api/v1/apps/"+id+"/config", { method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, credentials: "include", }) .then((response) => { if (response.status !== 200) { window.location.pathname = "/apps" } return response.json() }) .then((responseJson) => { if (!responseJson.success) { alert.error("Failed to download file") } else { const data = YAML.stringify(YAML.parse(responseJson.body)) var name = inputdata.name name = name.replace(/ /g, "_", -1) name = name.toLowerCase() var blob = new Blob( [ data ], { type: 'application/octet-stream' }) var url = URL.createObjectURL( blob ) var link = document.createElement( 'a' ) link.setAttribute( 'href', url ) link.setAttribute( 'download', `${name}.yaml` ) var event = document.createEvent( 'MouseEvents' ) event.initMouseEvent( 'click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null) link.dispatchEvent( event ) //link.parentNode.removeChild(link) } }) .catch(error => { console.log(error) alert.error(error.toString()) }); } // dropdown with copy etc I guess const appPaper = (data) => { var boxWidth = "2px" if (selectedApp.id === data.id) { boxWidth = "4px" } var boxColor = "orange" if (data.is_valid) { boxColor = "green" } var imageline = data.large_image.length === 0 ? Image missing : {data.title} // FIXME - add label to apps, as this might be slow with A LOT of apps var newAppname = data.name newAppname = newAppname.replace("_", " ") newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1) var sharing = "public" if (!data.sharing) { sharing = "private" } var valid = "true" if (!data.valid) { valid = "false" } if (data.actions === null || data.actions.length === 0) { valid = "false" } var description = data.description const maxDescLen = 60 if (description.length > maxDescLen) { description = data.description.slice(0, maxDescLen)+"..." } return ( { if (selectedApp.id !== data.id) { setSelectedApp(data) } }}> {imageline}

{newAppname}

{description}
Sharing: {sharing} , Valid: {valid}
{downloadApp(data)}}>
) } const dividerColor = "rgb(225, 228, 232)" const uploadViewPaperStyle = { minWidth: "100%", maxWidth: "100%", color: "white", backgroundColor: surfaceColor, display: "flex", marginBottom: 10, } //const handleFile = (event) =>{ // const formData = new FormData(); // formData.append('file', event.target.files[0]); // fetch(globalUrl+"/api/v1/workflows/apps/validate", { // method: 'POST', // headers: { // 'Accept': 'application/json', // }, // body: formData, // credentials: "include", // }) // .then((response) => { // if (response.status !== 200) { // console.log("Status not 200 for apps :O!") // return // } // return response.json() // }) // .then((responseJson) => { // console.log(responseJson) // }) // .catch(error => { // alert.error(error.toString()) // }); //} const UploadView = () => { //var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ? // // : // PICTURE // FIXME - add label to apps, as this might be slow with A LOT of apps var newAppname = selectedApp.name if (newAppname !== undefined && newAppname.length > 0) { newAppname = newAppname.replace("_", " ") newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1) } else { newAppname = "" } var description = selectedApp.description const url = "/apps/edit/"+selectedApp.id var editButton = selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ? : null var deleteButton = (selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated) || (selectedApp.downloaded != undefined && selectedApp.downloaded == true) ? : null //fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), { var baseInfo = newAppname.length > 0 ?

{newAppname}

{description}

URL: {selectedApp.link}

ID: {selectedApp.id}

PrivateID: {selectedApp.privateId}

{editButton} {deleteButton}
: null return(

App creation

What are apps?  - OpenAPI specification
Apps are how you interact with workflows, and are used to execute workflows. They are created with the app creator, using OpenAPI specification or manually in python.
{baseInfo}
) } const handleSearchChange = (event) => { const searchfield = event.target.value.toLowerCase() const newapps = apps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield)) setFilteredApps(newapps) } const appView = isLoggedIn ?

Upload

Available integrations

{isLoading ? : null}
{ handleSearchChange(event) }} />
{apps.length > 0 ? filteredApps.length > 0 ?
{filteredApps.map(app => { return ( appPaper(app) ) })}
:

Try a broader search term. E.g. "http" or "TheHive"

:

No apps have been created, uploaded or downloaded yet. Click "Load existing apps" above to get the baseline. This may take a while as its building docker images.

}
:

Available integrations

{apps.map(data => { return ( appPaper(data) ) })}
// Gets the URL itself (hopefully this works in most cases? // Will then forward the data to an internal endpoint to validate the api const getExistingApps = () => { setValidation(true) setIsLoading(true) start() alert.success("Downloading and building apps. Feel free to move around meanwhile.") var cors = "cors" fetch(globalUrl+"/api/v1/apps/get_existing", { method: "GET", mode: "cors", headers: { 'Accept': 'application/json', }, credentials: "include", }) .then((response) => { if (response.status !== 200) { alert.success("Failed loading.") } else { response.text().then(function (text) { console.log("RETURN: ", text) alert.success("Loaded existing apps!") }) } setIsLoading(false) stop() }) .catch(error => { alert.error(error.toString()) }) } // Gets the URL itself (hopefully this works in most cases? // Will then forward the data to an internal endpoint to validate the api const validateUrl = () => { setValidation(true) var cors = "cors" if (openApi.includes("localhost")) { cors = "no-cors" } fetch(openApi, { method: "GET", mode: "cors", }) .then((response) => { response.text().then(function (text) { validateOpenApi(text) }) }) .catch(error => { alert.error(error.toString()) }); } const deleteApp = (appId) => { alert.info("Attempting to delete app") fetch(globalUrl+"/api/v1/apps/"+appId, { method: 'DELETE', headers: { 'Accept': 'application/json', }, credentials: "include", }) .then((response) => { if (response.status === 200) { alert.success("Successfully deleted app") getApps() } else { alert.error("Failed deleting app") } }) .catch(error => { alert.error(error.toString()) }); } const validateRemote = () => { setValidation(true) fetch(globalUrl+"/api/v1/get_openapi_uri", { method: 'POST', headers: { 'Accept': 'application/json', }, body: JSON.stringify(openApi), credentials: "include", }) .then((response) => { return response.text() }) .then((responseText) => { validateOpenApi(responseText) setValidation(false) }) .catch(error => { alert.error(error.toString()) }); } const escapeApiData = (apidata) => { console.log(apidata) try { return JSON.stringify(JSON.parse(apidata)) } catch(error) { console.log("JSON DECODE ERROR - TRY YAML") } try { return JSON.stringify(YAML.parse(apidata)) } catch(error) { console.log("YAML DECODE ERROR - TRY SOMETHING ELSE?: "+error) setOpenApiError(error) } return "" } // Sends the data to backend, which should return a version 3 of the same API // If 200 - continue, otherwise, there's some issue somewhere const validateOpenApi = (openApidata) => { const newApidata = escapeApiData(openApidata) if (newApidata === "") { return } fetch(globalUrl+"/api/v1/validate_openapi", { method: 'POST', headers: { 'Accept': 'application/json', }, body: newApidata, credentials: "include", }) .then((response) => { return response.json() }) .then((responseJson) => { setValidation(false) if (responseJson.success) { setAppValidation(responseJson.id) } else { if (responseJson.reason !== undefined) { setOpenApiError(responseJson.reason) } alert.error("An error occurred in the response") } }) .catch(error => { alert.error(error.toString()) }); } const redirectOpenApi = () => { window.location.href = "/apps/new?id="+appValidation } const errorText = openApiError.length > 0 ?
Error: {openApiError}
: null const circularLoader = validation ? : null const modalView = openApiModal ? {setOpenApiModal(false)}} PaperProps={{ style: { backgroundColor: surfaceColor, color: "white", minWidth: "800px", minHeight: "320px", }, }} >
Create a new integration
Paste in the URI for the OpenAPI { setOpenApiError("") validateRemote() }}>Validate }} onChange={e => setOpenApi(e.target.value)} helperText={
Must point to a version 2 or 3 specification.
} placeholder="OpenAPI URI" fullWidth />
Example:
https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json

or paste the yaml/JSON directly below

{ setOpenApiError("") validateOpenApi(openApiData) }}>Validate data }} onChange={e => setOpenApiData(e.target.value)} helperText={
Must point to a version 2 or 3 specification.
} placeholder="OpenAPI text" fullWidth /> {errorText} {circularLoader}
: null const loadedCheck = isLoaded && !firstrequest ?
{appView} {modalView}
:
// Maybe use gridview or something, idk return (
{loadedCheck}
) } export default Apps