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 baseRepository = "https://github.com/frikky/shuffle-apps" 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 [loadAppsModalOpen, setLoadAppsModalOpen] = React.useState(false); const [openApiModal, setOpenApiModal] = React.useState(false); const [openApiModalType, setOpenApiModalType] = React.useState(""); const [openApiError, setOpenApiError] = React.useState("") const [field1, setField1] = React.useState("") const [field2, setField2] = React.useState("") const { start, stop } = useInterval({ duration: 5000, startImmediate: false, callback: () => { getApps() } }); useEffect(() => { if (apps.length <= 0 && firstrequest) { document.title = "Shuffle - Apps" if (!isLoggedIn && isLoaded) { window.location = "/login" } 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 Creator

Security API's  - OpenAPI directory
Apps interact with eachother in workflows. They are created with the app creator, using OpenAPI specification or manually in python. Use the links above to find potential apps you're looking for using OpenAPI or make one from scratch. There's 1000+ available.
{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.

}
: null // Load data e.g. from github const getSpecificApps = (url) => { setValidation(true) setIsLoading(true) start() const parsedData = { "url": url, } if (field1.length > 0) { parsedData["field_1"] = field1 } if (field2.length > 0) { parsedData["field_2"] = field2 } alert.success("Getting specific apps from your URL.") var cors = "cors" fetch(globalUrl+"/api/v1/apps/get_existing", { method: "POST", mode: "cors", headers: { 'Accept': 'application/json', }, body: JSON.stringify(parsedData), credentials: "include", }) .then((response) => { if (response.status === 200) { response.text().then(function (text) { console.log("RETURN: ", text) alert.success("Loaded existing apps!") }) } setIsLoading(false) stop() return response.json() }) .then((responseJson) => { console.log("DATA: ", responseJson) if (responseJson.reason !== undefined) { alert.error("Failed loading: "+responseJson.reason) } else { alert.error("Failed loading") } }) .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 handleGithubValidation = () => { getSpecificApps(openApi) setLoadAppsModalOpen(false) } const appsModalLoad = loadAppsModalOpen ? { setOpenApi("") setLoadAppsModalOpen(false) setField1("") setField2("") }} PaperProps={{ style: { backgroundColor: surfaceColor, color: "white", minWidth: "800px", minHeight: "320px", }, }} >
Load from github repo
Repository (supported: github, gitlab, bitbucket) setOpenApi(e.target.value)} placeholder="https://github.com/frikky/shuffle-apps" fullWidth /> Authentication (optional - private repos etc):
setField1(e.target.value)} type="username" placeholder="Username / APIkey (optional)" fullWidth /> setField2(e.target.value)} type="password" placeholder="Password (optional)" fullWidth />
{circularLoader}
: null 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} {appsModalLoad}
:
// Maybe use gridview or something, idk return (
{loadedCheck}
) } export default Apps