var imageline =
data.large_image === undefined || data.large_image.length === 0 ? (
) : (
{
//console.log("IMG LOADED!: ", event.target)
}}
/>
);
var newAppname = data.name;
if (newAppname === undefined) {
newAppname = "Undefined";
} else {
newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1);
newAppname = newAppname.replaceAll("_", " ");
}
var sharing = "public";
if (!data.sharing) {
sharing = "private";
}
var valid = "true";
if (!data.valid) {
valid = "false";
}
if (data.actions === undefined || data.actions === null) {
// Check if data type undefined/bool
if (typeof data === "boolean") {
data = {}
}
data.actions = []
}
if (data === undefined || data.actions === undefined || 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) + "...";
}
const version = data.app_version;
return (
{
if (selectedApp.id !== data.id) {
if (data.owner !== undefined && data.owner !== null) {
getUserProfile(data.owner);
}
setContact(data.contact_info)
data.name = newAppname;
setSelectedApp(data);
setSharingConfiguration(data.sharing === true ? "public" : "you")
if (
data.actions !== undefined &&
data.actions !== null &&
data.actions.length > 0
) {
setSelectedAction(data.actions[0]);
} else {
setSelectedAction({});
}
if (data.sharing) {
setSharingConfiguration("public");
}
}
}}
>
{imageline}
{newAppname}
{description}
{data.tags === null || data.tags === undefined
? null
: data.tags.map((tag, index) => {
if (index >= 3) {
return null;
}
return (
{
//console.log("SEARCH: ", event.target.value)
handleSearchChange(tag);
setCursearch(tag);
//id="app_search_field"
const searchfield =
document.getElementById("app_search_field");
if (
searchfield !== null &&
searchfield !== undefined
) {
console.log("SEARCHFIELD: ", searchfield);
searchfield.value = tag;
}
}}
/>
);
})}
{data.activated &&
data.private_id !== undefined &&
data.private_id.length > 0 &&
data.generated ? (
{
downloadApp(data);
}}
>
) : null}
);
};
const dividerColor = "rgb(225, 228, 232)";
const uploadViewPaperStyle = {
minWidth: viewWidth,
maxWidth: viewWidth,
color: "white",
borderRadius: theme.palette?.borderRadius,
backgroundColor: surfaceColor,
//display: "flex",
marginBottom: 10,
overflow: "hidden",
};
const UploadView = () => {
//var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ?
//
// :
//
// 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.replaceAll("_", " ");
newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1);
} else {
newAppname = "";
}
var description = selectedApp.description;
const editUrl = "/apps/edit/" + selectedApp.id;
const activateUrl = "/apps/new?id=" + selectedApp.id;
var downloadButton =
selectedApp.activated &&
selectedApp.private_id !== undefined &&
selectedApp.private_id.length > 0 &&
selectedApp.generated ? (
{
downloadApp(selectedApp);
}}
variant="outlined"
component="label"
color="primary"
style={{ marginTop: 10, marginRight: 8 }}
>
) : null;
// Should always reference the original ID.
//if (selectedApp.name !== undefined && selectedApp.name !== null && selectedApp.name.includes("New")) {
//}
var editButton =
selectedApp.activated &&
selectedApp.private_id !== undefined &&
selectedApp.private_id.length > 0 &&
selectedApp.generated ? (
) : null;
//var editNewButton = editButton === null ?
var editNewButton = selectedApp.generated && selectedApp.activated && props.userdata.id !== selectedApp.owner && isCloud ?
: null
const activateButton =
selectedApp.generated && !selectedApp.activated ? (
Activate App
{
setDeleteModalOpen(true);
}}
disabled={sharingConfiguration === undefined || sharingConfiguration === null || sharingConfiguration == "public"}
>
) : null;
const deleteButton =
((selectedApp.private_id !== undefined &&
selectedApp.private_id.length > 0 &&
selectedApp.generated) ||
(selectedApp.downloaded !== undefined && selectedApp.downloaded == true) ||
!selectedApp.generated) &&
activateButton === null ? (
{
setDeleteModalOpen(true);
}}
disabled={sharingConfiguration === undefined || sharingConfiguration === null || sharingConfiguration == "public"}
>
) : null;
var imageline =
selectedApp.large_image === undefined ||
selectedApp.large_image.length === 0 ? (
) : (
);
const GetAppExample = () => {
if (selectedAction.returns === undefined) {
return null;
}
var showResult = selectedAction.returns.example;
if (
showResult === undefined ||
showResult === null ||
showResult.length === 0
) {
return null;
}
var jsonvalid = true;
try {
const tmp = String(JSON.parse(showResult));
if (!tmp.includes("{") && !tmp.includes("[")) {
jsonvalid = false;
}
} catch (e) {
jsonvalid = false;
}
// FIXME: In here -> parse the values into a list or something
if (jsonvalid) {
const paths = GetParsedPaths(JSON.parse(showResult), "");
console.log("PATHS: ", paths);
return (
{paths.map((data, index) => {
const circleSize = 10;
return (
console.log(data.autocomplete)}
>
{data.name}
);
})}
);
}
return (
Example return
{selectedAction.returns.example}
);
};
const userRoles = ["you", "public"];
// Admin in org or creator of app
// FIXME: Missing check for if same creator account
const canEditApp = userdata !== undefined && (userdata.admin === "true" || userdata.id === selectedApp.owner || selectedApp.owner === "" || (userdata.admin === "true" && userdata.active_org.id === selectedApp.reference_org)) || !selectedApp.generated
//fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"),
var baseInfo =
newAppname.length > 0 ? (
{imageline}
{newAppname}
Version {selectedApp.app_version}
{description}
{selectedApp.versions !== null &&
selectedApp.versions !== undefined &&
selectedApp.versions.length > 1 ? (
{
console.log("Changing version to index " + event.target.value);
const newversion = selectedApp.versions.find(
(tmpApp) => tmpApp.version == event.target.value
);
console.log("New version: ", newversion);
selectedApp.app_version = selectedApp.app_version;
setSelectedApp(selectedApp);
setSharingConfiguration(selectedApp.sharing === true ? "public" : "you")
if (newversion !== undefined && newversion !== null) {
getApp(newversion.id, true);
}
}}
style={{
position: "absolute",
top: -10,
right: isCloud ? 50 : 0,
backgroundColor: inputColor,
color: "white",
height: 35,
marginleft: 10,
}}
SelectDisplayProps={{
style: {
marginLeft: 10,
},
}}
>
{selectedApp.versions.map((data, index) => {
return (
{data.version}
);
})}
) : null}
{isCloud ? (
) : null}
{activateButton}
{ /* editNewButton === null && */ }
{canEditApp ? (
{editButton}
{downloadButton}
{deleteButton}
) :
{editNewButton}
}
{canEditApp
? (
{/*
ID: {selectedApp.id}
*/}
Sharing
{
setSharingConfiguration(event.target.value);
if (event.target.value === "you") {
toast("Changing sharing to " + event.target.value);
updateAppField(selectedApp.id, "sharing", false);
} else if (
event.target.value === "everyone" ||
event.target.value === "public"
) {
if (!isCloud) {
setPublishModalOpen(true)
} else {
updateAppField(selectedApp.id, "sharing", true);
}
} else {
console.log(
"Can't handle value for sharing: ",
event.target.value
);
}
}}
style={{
width: 150,
backgroundColor: theme.palette.surfaceColor,
color: "white",
height: 35,
marginleft: 10,
}}
SelectDisplayProps={{
style: {
marginLeft: 10,
},
}}
>
{userRoles.map((data) => {
return (
{data}
);
})}
{/*isCloud && (selectedApp.sharing === true || selectedApp.public === true || creatorProfile.github_avatar !== undefined) && !internalIds.includes(selectedApp.name.toLowerCase()) */}
{isCloud && !internalIds.includes(selectedApp.name.toLowerCase()) ?
{
if (selectedApp.reference_org === userdata.active_org.id) {
toast.info("Can't deactivate apps made in this org. Please contact support if you want to deactivate this app.")
return
}
const tmpurl = new URL(window.location.href);
const searchParams = tmpurl.searchParams;
const queryID = searchParams.get("queryID");
if (queryID !== undefined && queryID !== null) {
aa("init", {
appId: "JNSS5CFDZZ",
apiKey: "c8f882473ff42d41158430be09ec2b4e",
});
const timestamp = new Date().getTime();
aa("sendEvents", [
{
eventType: "conversion",
eventName: "Public App Activated",
index: "appsearch",
objectIDs: [selectedApp.id],
timestamp: timestamp,
queryID: queryID,
userToken:
userdata === undefined ||
userdata === null ||
userdata.id === undefined
? "unauthenticated"
: userdata.id,
},
]);
} else {
console.log("No query to handle when activating");
}
activateApp(selectedApp.id, true, true)
}}
style={{ height: 35, marginTop: 0, marginLeft: 10, }}
>
Deactivate
: null}
) : null}
{isCloud && Object.getOwnPropertyNames(creatorProfile).length !== 0 && creatorProfile.github_avatar !== undefined && creatorProfile.github_avatar !== null ?
{
//setAnchorElAvatar(event.currentTarget);
}}
>
Shared by{" "}
{creatorProfile.github_username}
: null}
{selectedApp.tags !== undefined && selectedApp.tags !== null ? (
{selectedApp.tags.map((tag, index) => {
if (index >= 3) {
return null;
}
return (
);
})}
) : null}
{/*
Owner: {selectedApp.owner}
*/}
{selectedApp.privateId !== undefined &&
selectedApp.privateId.length > 0 ? (
PrivateID: {selectedApp.privateId}
) : null}
{selectedApp.link.length > 0 ? (
URL: {selectedApp.link}
) : null}
Actions
{selectedApp.actions !== null &&
selectedApp.actions.length > 0 ? (
{
setSelectedAction(event.target.value);
}}
style={{
backgroundColor: inputColor,
color: "white",
height: "50px",
}}
SelectDisplayProps={{
style: {
marginLeft: 10,
},
}}
>
{selectedApp.actions.map((data) => {
var newActionname =
data.label !== undefined && data.label.length > 0
? data.label
: data.name;
newActionname = newActionname.replaceAll("_", " ");
newActionname =
newActionname.charAt(0).toUpperCase() +
newActionname.substring(1);
return (
{newActionname}
);
})}
) : (
There are no actions defined for this app.
)}
{selectedAction.parameters !== undefined &&
selectedAction.parameters !== null ? (
Parameters
{selectedAction.parameters.map((data) => {
var itemColor = "#f85a3e";
if (!data.required) {
itemColor = "#ffeb3b";
}
const circleSize = 10;
return (
{data.configuration === true ? (
) : (
)}
{data.name}
);
})}
) : null}
{selectedAction.description !== undefined &&
selectedAction.description !== null &&
selectedAction.description.length > 0 ? (
Action Description
{selectedAction.description}
) : null}
) : null;
const AppCreateButton = (props) => {
const { text, func, icon } = props;
const [hover, setHover] = React.useState(false);
const makeFancy = text?.includes("Generate")
var parsedStyle = {
flex: 1,
padding: 15,
margin: 10,
paddingTop: 25,
backgroundColor: hover ? theme.palette.surfaceColor : "transparent",
cursor: hover ? "pointer" : "default",
textAlign: "center",
minHeight: 150,
maxHeight: 150,
borderRadius: theme.palette?.borderRadius,
}
if (!makeFancy) {
parsedStyle.border = hover ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)"
} else {
parsedStyle.border = "1px solid transparent"
parsedStyle.borderImage = "linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet) 1"
parsedStyle.borderRadius = 0 // This doesn't work. Try to hover with a high one, and it's weird due to borderImage
}
return (
setHover(true)}
onMouseLeave={() => setHover(false)}
onClick={func}
style={parsedStyle}
>
{icon}
{text}
)
}
return (
App Creator
{
setOpenApiModal(true)
}}
icon={ }
/>
{
setGenerateAppModal(true)
}}
icon={ }
/>
Create from scratch
{/*
How it works
-{" "}
Security API's
-{" "}
OpenAPI directory
-{" "}
OpenAPI Validator
Apps interact with eachother in workflows. They are created with
the app creator, using OpenAPI specification or manually in
python. The links above are references to OpenAPI tools and other
app repositories. There's thousands of them.
{
setOpenApiModal(true);
}}
>
Create from OpenAPI
OR
Create from scratch
*/}
{baseInfo}
);
};
const handleSearchChange = (search) => {
if (apps === undefined || apps === null || apps.length === 0) {
return;
}
const searchfield = search.toLowerCase();
var newapps = apps.filter(
(data) =>
data.name.toLowerCase().includes(searchfield) ||
data.description.toLowerCase().includes(searchfield) ||
(data.tags !== null && data.tags.includes(search))
);
var tmpapps = searchableApps.filter(
(data) =>
data.name.toLowerCase().includes(searchfield) ||
data.description.toLowerCase().includes(searchfield) ||
(data.tags !== null && data.tags.includes(search))
);
newapps.push(...tmpapps);
//console.log(newapps)
setFilteredApps(newapps);
//if ((newapps.length === 0 || searchBackend) && !appSearchLoading) {
// //setAppSearchLoading(true)
// //runAppSearch(searchfield)
//} else {
//}
};
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;
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");
}
};
useEffect(() => {
if (openApiData.length > 0) {
setOpenApiError("");
validateOpenApi(openApiData);
}
}, [openApiData]);
useEffect(() => {
if (appValidation && isDropzone) {
redirectOpenApi();
setIsDropzone(false);
}
}, [appValidation, isDropzone]);
var appDelay = -75
const leftBarSize = viewWidth
const SearchBox = ({ currentRefinement, refine, isSearchStalled, }) => {
useEffect(() => {
if (document !== undefined) {
const appsearchValue = document.getElementById("app_search_field")
if (appsearchValue !== undefined && appsearchValue !== null) {
console.log("Value2: ", appsearchValue.value)
if (appsearchValue.value !== undefined && appsearchValue.value !== null && appsearchValue.value.length > 0) {
refine(appsearchValue.value)
}
}
//}
}
}, [])
return (
)
}
const activateApp = (appid, refresh, deactivate) => {
const appExists = userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appid)
const url = deactivate === true ?
`${globalUrl}/api/v1/apps/${appid}/deactivate`
:
appExists ? `${globalUrl}/api/v1/apps/${appid}/deactivate` : `${globalUrl}/api/v1/apps/${appid}/activate`
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Failed to deactivate")
}
return response.json()
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
toast("Failed to activate the app: "+responseJson.reason);
} else {
toast("Failed to activate the app");
}
} else {
//toast("App activated for your organization! Refresh the page to use the app.")
if (appExists) {
toast("App deactivated for your organization! Existing workflows with the app will continue to work.")
} else {
toast("App activation changed for your organization!")
}
if (refresh === true) {
getApps()
}
}
})
.catch(error => {
//toast(error.toString())
console.log("Deactivate app error: ", error.toString())
});
}
const AppHits = ({ hits }) => {
const [mouseHoverIndex, setMouseHoverIndex] = React.useState(0)
//var tmp = searchOpen
//if (!searchOpen) {
// return null
//}
const positionInfo = document.activeElement.getBoundingClientRect()
const outerlistitemStyle = {
width: "100%",
overflowX: "hidden",
overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)",
}
if (hits.length > 4) {
hits = hits.slice(0, 4)
}
var type = "app"
const baseImage =
return (
{hits.length === 0 ?
console.log(hits)}>
:
hits.map((hit, index) => {
const innerlistitemStyle = {
width: positionInfo.width + 35,
overflowX: "hidden",
overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)",
backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit",
cursor: "pointer",
marginLeft: 0,
marginRight: 0,
maxHeight: 75,
minHeight: 75,
maxWidth: 420,
minWidth: "100%",
}
const name = hit.name === undefined ?
hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title :
(hit.name.charAt(0).toUpperCase() + hit.name.slice(1)).replaceAll("_", " ")
var secondaryText = hit.data !== undefined ? hit.data.slice(0, 40) + "..." : ""
const avatar = hit.image_url === undefined ?
baseImage
:
//console.log(hit)
if (hit.categories !== undefined && hit.categories !== null && hit.categories.length > 0) {
secondaryText = hit.categories.slice(0, 3).map((data, index) => {
if (index === 0) {
return data
}
return ", " + data
/*
{
//handleChipClick
}}
variant="outlined"
color="primary"
/>
*/
})
}
var parsedUrl = isCloud ? `/apps/${hit.objectID}` : `https://shuffler.io/apps/${hit.objectID}`
parsedUrl += `?queryID=${hit.__queryID}`
return (
{
//if (!isCloud) {
// toast("Since this is an on-prem instance. You will need to activate the app yourself. Opening link to download it in a new window.")
// setTimeout(() => {
// event.preventDefault()
// window.open(parsedUrl, '_blank')
// }, 2000)
//} else {
toast(`Activating ${name}`)
//}
console.log("CLICK: ", hit)
const queryID = hit.__queryID
console.log("QUERY: ", queryID)
if (queryID !== undefined && queryID !== null) {
aa('init', {
appId: "JNSS5CFDZZ",
apiKey: "c8f882473ff42d41158430be09ec2b4e",
})
const timestamp = new Date().getTime()
aa('sendEvents', [
{
eventType: 'conversion',
eventName: 'Public App Activated',
index: 'appsearch',
objectIDs: [hit.objectID],
timestamp: timestamp,
queryID: queryID,
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
}
])
}
activateApp(hit.objectID, true)
}}>
{
setMouseHoverIndex(index)
}}>
{avatar}
{/*
*/}
)
})
}
)
}
const CustomSearchBox = connectSearchBox(SearchBox)
const CustomAppHits = connectHits(AppHits)
const appView = isLoggedIn ? (
App upload
{selectedApp.activated &&
selectedApp.private_id !== undefined &&
selectedApp.private_id.length > 0 &&
selectedApp.generated ? (
{FixName(selectedApp.name)}
) : null}
Activated apps ({apps.length + searchableApps.length})
{isCloud ? null : (
{userdata === undefined || userdata === null || isLoading ? null : (
{
hotloadApps();
}}
>
{isLoading ? (
) : (
)}
)}
{userdata === undefined || userdata === null || userdata.admin === "false" ? null :
{
setOpenApi(baseRepository);
setLoadAppsModalOpen(true);
}}
>
{isLoading ? (
) : (
)}
}
)}
{
handleSearchChange(event.target.value);
setCursearch(event.target.value);
}}
/>
{apps.length > 0 ? (
filteredApps.length > 0 ? (
{filteredApps.map((app, index) => {
if (firstLoad) {
appDelay += 75
} else {
//return returnData
return
}
return (
)
})}
{cursearch.length > 0
? null
: searchableApps.map((app, index) => {
return (
)
})}
) : (
{appSearchLoading ? (
) : null}
{
console.log("Should load in extra apps?")
}}
>
Couldn't find the app you're looking for? Searching unactivated apps. Click one of the below apps to Activate it for your organization.
{
console.log("CLICKED")
}}>
)
) : isLoading ? (
) : (
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.
If you're still not able to see any apps, please follow our{" "}
troubleshooting guide for loading apps!
)}
) : null;
// Load data e.g. from github
const getSpecificApps = (url, forceUpdate) => {
setValidation(true);
setIsLoading(true);
//start()
const parsedData = {
url: url,
branch: downloadBranch || "master",
};
if (field1.length > 0) {
parsedData["field_1"] = field1;
}
if (field2.length > 0) {
parsedData["field_2"] = field2;
}
parsedData["force_update"] = forceUpdate;
toast("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) {
toast("Loaded existing apps!");
}
//stop()
setIsLoading(false);
setValidation(false);
return response.json();
})
.then((responseJson) => {
console.log("DATA: ", responseJson);
if (responseJson.reason !== undefined) {
toast("Failed loading: " + responseJson.reason);
}
})
.catch((error) => {
console.log("ERROR: ", error.toString());
//toast(error.toString());
//stop()
setIsLoading(false);
setValidation(false);
});
};
// Locally hotloads app from folder
const hotloadApps = () => {
toast("Hotloading apps from location in .env");
setIsLoading(true);
fetch(globalUrl + "/api/v1/apps/run_hotload", {
method: "POST",
mode: "cors",
headers: {
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
setIsLoading(false);
if (response.status === 200) {
//toast("Hotloaded apps!")
getApps();
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === true) {
toast("Successfully finished hotload");
} else {
toast("Failed hotload: ", responseJson.reason);
//(responseJson.reason !== undefined && responseJson.reason.length > 0) {
}
})
.catch((error) => {
toast(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) => {
toast(error.toString());
});
};
const getApp = (appId, setApp) => {
fetch(globalUrl + "/api/v1/apps/" + appId + "/config?openapi=false", {
headers: {
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status === 200) {
//toast("Successfully GOT app "+appId)
} else {
toast("Failed getting app");
}
return response.json();
})
.then((responseJson) => {
console.log(responseJson);
if (setApp) {
if (
selectedApp.versions !== undefined &&
selectedApp.versions !== null
) {
responseJson.versions = selectedApp.versions;
}
if (
selectedApp.loop_versions !== undefined &&
selectedApp.loop_versions !== null
) {
responseJson.loop_versions = selectedApp.loop_versions;
}
//toast("Should set app to selected")
if (
responseJson.actions !== undefined &&
responseJson.actions !== null &&
responseJson.actions.length > 0
) {
setSelectedAction(responseJson.actions[0]);
} else {
setSelectedAction({})
}
setSelectedApp(responseJson);
setSharingConfiguration(responseJson.sharing === true ? "public" : "you")
}
})
.catch((error) => {
toast(error.toString());
});
};
const deleteApp = (appId) => {
toast("Attempting to delete app");
fetch(globalUrl + "/api/v1/apps/" + appId, {
method: "DELETE",
headers: {
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status === 200) {
toast("Successfully deleted app");
setTimeout(() => {
getApps();
}, 1000);
} else {
toast("Failed deleting app. Does it still exist?");
}
})
.catch((error) => {
toast(error.toString());
});
};
const updateAppField = (app_id, fieldname, fieldvalue) => {
const data = {};
data[fieldname] = fieldvalue;
console.log("DATA: ", data);
fetch(globalUrl + "/api/v1/apps/" + app_id, {
method: "PATCH",
headers: {
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
})
.then((response) => {
//setAppSearchLoading(false)
return response.json();
})
.then((responseJson) => {
//console.log(responseJson)
//toast(responseJson)
if (responseJson.success) {
toast("Successfully updated app configuration");
} else {
if (responseJson.reason !== undefined && responseJson.reason !== null) {
toast("Error: "+responseJson.reason);
} else {
toast("Error updating app configuration. Are you the owner of this app?");
}
}
})
.catch((error) => {
toast(error.toString());
});
};
const runAppSearch = (searchterm) => {
const data = { search: searchterm };
fetch(globalUrl + "/api/v1/apps/search", {
method: "POST",
headers: {
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
})
.then((response) => {
setAppSearchLoading(false);
return response.json();
})
.then((responseJson) => {
if (responseJson.success) {
if (
responseJson.reason !== undefined &&
responseJson.reason.length > 0
) {
setSearchableApps(responseJson.reason);
//setFilteredApps(responseJson.reason)
}
}
})
.catch((error) => {
toast(error.toString());
});
};
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", {
fetch("https://doc-to-openapi-stbuwivzoq-nw.a.run.app/api/v1/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
var parsedtext = responseJson
try {
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(parsedtext)
})
.catch((error) => {
setValidation(false);
toast(error.toString());
setOpenApiError(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) => {
setValidation(false);
if (response.status !== 200) {
return response.json();
}
return response.text();
})
.then((responseJson) => {
if (typeof responseJson !== "string" && !responseJson.success) {
console.log(responseJson.reason);
if (responseJson.reason !== undefined) {
setOpenApiError(responseJson.reason);
} else {
setOpenApiError("Undefined issue with OpenAPI validation");
}
return;
}
console.log("Validating response!");
validateOpenApi(responseJson);
})
.catch((error) => {
toast(error.toString());
setOpenApiError(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 {
const parsed = YAML.parse(YAML.stringify(apidata));
//const parsed = YAML.parse(apidata))
return YAML.stringify(parsed);
} catch (error) {
console.log("YAML DECODE ERROR - TRY SOMETHING ELSE?: " + error);
setOpenApiError("Local error: " + error.toString());
}
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) => {
var newApidata = escapeApiData(openApidata);
if (newApidata === "") {
// Used to return here
newApidata = openApidata;
return;
}
//console.log(newApidata)
setValidation(true);
fetch(globalUrl + "/api/v1/validate_openapi", {
method: "POST",
headers: {
Accept: "application/json",
},
body: openApidata,
credentials: "include",
})
.then((response) => {
setValidation(false);
return response.json();
})
.then((responseJson) => {
if (responseJson.success) {
setAppValidation(responseJson.id);
} else {
if (responseJson.reason !== undefined) {
setOpenApiError(responseJson.reason);
}
toast("An error occurred in the response");
}
})
.catch((error) => {
setValidation(false);
toast(error.toString());
setOpenApiError(error.toString());
});
};
const redirectOpenApi = () => {
if (appValidation === undefined || appValidation === null || appValidation.length === 0) {
return
}
toast.success("Successfully validated OpenAPI. Redirecting to app creation. Remember to save the app to be able to use it.", {
// Disable autoclose
autoClose: 10000,
})
navigate(`/apps/new?id=${appValidation}`)
}
const handleGithubValidation = (forceUpdate) => {
getSpecificApps(openApi, forceUpdate);
setLoadAppsModalOpen(false);
};
const publishModal = publishModalOpen ? (
{
setPublishModalOpen(false);
}}
PaperProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 500,
padding: 50,
},
}}
>
Are you sure you want to PUBLISH this app?
Before publishing, make sure to sanitize the App for anything you don't want public.
The published App is yours, and you can always change your public Apps after they are released.
{
updateAppField(selectedApp.id, "sharing", true);
setPublishModalOpen(false);
}}
color="primary"
>
Yes
{
setPublishModalOpen(false);
}}
color="primary"
>
No
) : null;
const deleteModal = deleteModalOpen ? (
{
setDeleteModalOpen(false);
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: 500,
},
}}
>
Are you sure?
Some workflows may stop working.
{
deleteApp(selectedApp.id);
setDeleteModalOpen(false);
}}
color="primary"
>
Yes
{
setDeleteModalOpen(false);
}}
color="primary"
>
No
) : null;
const circularLoader = validation ? (
) : null;
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
/>
Branch (default value is "master"):
setDownloadBranch(e.target.value)}
placeholder="master"
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}
setLoadAppsModalOpen(false)}
color="primary"
>
Cancel
{isCloud ? null : (
{
handleGithubValidation(true);
}}
color="primary"
>
Force update
)}
{
handleGithubValidation(false);
}}
color="primary"
>
Submit
) : null;
const errorText = openApiError.length > 0 ? (
Error: {openApiError}
) : null;
const generateAppView = generateAppModal ? (
{
setGenerateAppModal(false);
setOpenApiModal(false);
}}
PaperProps={{
style: {
backgroundColor: theme.palette.platformColor,
borderRadius: theme.palette?.borderRadius,
color: "white",
minWidth: "800px",
minHeight: "320px",
padding: 50,
},
}}
>
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 Shuffle Cloud (https://shuffler.io) for processing (for now)."}
0 || validation}
color="primary"
onClick={() => {
setOpenApiError("");
validateDocumentationUrl();
}}
>
Generate
),
}}
onChange={(e) => {
setOpenApi(e.target.value);
}}
helperText={
Should be a documentation page containing an API.
}
placeholder="API Documentation URL"
fullWidth
/>
{errorText}
{circularLoader}
{
setGenerateAppModal(false);
setOpenApiModal(false);
setAppValidation("");
setOpenApiError("");
setOpenApi("");
setOpenApiData("");
}}
color="primary"
>
Cancel
) : null
const modalView = openApiModal ? (
{
setOpenApiModal(false);
setGenerateAppModal(false);
}}
PaperProps={{
style: {
backgroundColor: theme.palette.platformColor,
borderRadius: theme.palette?.borderRadius,
color: "white",
minWidth: "800px",
minHeight: "320px",
padding: 50,
},
}}
>
Create a new app from OpenAPI / Swagger
Paste in the URI for the OpenAPI
0}
color="primary"
onClick={() => {
setOpenApiError("");
validateRemote();
}}
>
Validate
),
}}
onChange={(e) => {
setOpenApi(e.target.value);
}}
helperText={
Must point to a version 2 or 3 OpenAPI specification.
}
placeholder="OpenAPI URI"
fullWidth
/>
{/*
Example:
https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json
*/}
Or upload a YAML or JSON specification
upload.current.click()}
>
Upload
{errorText}
{circularLoader}
{
setOpenApiModal(false);
setAppValidation("");
setOpenApiError("");
setOpenApi("");
setOpenApiData("");
}}
color="primary"
>
Cancel
{
redirectOpenApi();
}}
color="primary"
>
Continue
) : null;
const loadedCheck = isLoaded && !firstrequest ? (
) : (
);
return loadedCheck;
};
export default Apps;
const AppsWrapper = memo(({ appView, modalView, userdata, publishModal, generateAppView, appsModalLoad, deleteModal }) => (
<>
{appView}
{modalView}
{publishModal}
{generateAppView}
{appsModalLoad}
{deleteModal}
>
));
const SidebarAdjustWrapper = memo(({ children }) => {
const {leftSideBarOpenByClick } = useContext(Context)
return (
{children}
);
});