Ton of minor fixes all pulled in. More info in the latest release for 1.2.0

This commit is contained in:
frikky
2023-05-15 04:42:09 +02:00
parent 70edb366a9
commit ee48543350
25 changed files with 1497 additions and 335 deletions
+3 -1
View File
@@ -10,7 +10,7 @@ import GettingStarted from "./views/GettingStarted";
import EditWebhook from "./views/EditWebhook";
import AngularWorkflow from "./views/AngularWorkflow";
import Header from "./components/Header";
import Header from "./components/Header.jsx";
import theme from "./theme";
import Apps from "./views/Apps";
import AppCreator from "./views/AppCreator";
@@ -336,7 +336,9 @@ const App = (message, props) => {
userdata={userdata}
{...props}
/>
{/*
<div style={{ height: 60 }} />
*/}
<Routes>
<Route
exact
+8 -6
View File
@@ -145,9 +145,11 @@ const EditWorkflow = (props) => {
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
minWidth: isMobile ? "90%" : 550,
maxWidth: isMobile ? "90%" : 550,
minHeight: 400,
//minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
//maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
},
}}
>
@@ -196,7 +198,7 @@ const EditWorkflow = (props) => {
</div>
: null}
</div>
{newWorkflow === true ?
{/*newWorkflow === true ?
<div style={{flex: 1, marginLeft: 45, }}>
<Typography variant="h6">
Use a Template
@@ -205,7 +207,7 @@ const EditWorkflow = (props) => {
Start your workflow from our templating system. This uses publied workflows from our <a href="/creators" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Creators</a> to generate full Usecases or parts of your Workflow.
</Typography>
</div>
: null}
: null*/}
</div>
</DialogTitle>
<FormControl>
@@ -438,7 +440,7 @@ const EditWorkflow = (props) => {
</IconButton>
</Tooltip>
</div>
{newWorkflow === true ?
{/*newWorkflow === true ?
<div style={{marginLeft: 50, maxWidth: 400, minWidth: 400, position: "relative",}}>
<UsecaseSearch
globalUrl={globalUrl}
@@ -449,7 +451,7 @@ const EditWorkflow = (props) => {
userdata={userdata}
/>
</div>
: null}
: null*/}
</DialogContent>
<DialogActions>
<Button
+35 -12
View File
@@ -1,4 +1,5 @@
import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import { useParams, useNavigate, Link } from "react-router-dom";
import { useTheme } from "@material-ui/core/styles";
import theme from '../theme';
@@ -75,6 +76,7 @@ const registeredApps = [
"jira",
"jira_service_desk",
"jira_service_management",
"github",
]
const AuthenticationOauth2 = (props) => {
@@ -92,8 +94,11 @@ const AuthenticationOauth2 = (props) => {
isCloud,
autoAuth,
authButtonOnly,
isLoggedIn,
} = props;
let navigate = useNavigate();
//const [update, setUpdate] = React.useState("|")
const [defaultConfigSet, setDefaultConfigSet] = React.useState(
authenticationType.client_id !== undefined &&
@@ -113,7 +118,7 @@ const AuthenticationOauth2 = (props) => {
const [oauthUrl, setOauthUrl] = React.useState("");
const [buttonClicked, setButtonClicked] = React.useState(false);
const [offlineAccess, setOfflineAccess] = React.useState(true);
const [offlineAccess, setOfflineAccess] = React.useState(false);
const allscopes = authenticationType.scope !== undefined ? authenticationType.scope : [];
@@ -134,8 +139,13 @@ const AuthenticationOauth2 = (props) => {
active: true,
});
useEffect(() => {
console.log("Should automatically click the auto-auth button?")
if (isLoggedIn === false) {
navigate(`/login?view=${window.location.pathname}&message=Log in to authenticate this app`)
}
console.log("Should automatically click the auto-auth button?: ", autoAuth)
if (autoAuth === true && selectedApp !== undefined) {
startOauth2Request()
}
@@ -147,7 +157,8 @@ const AuthenticationOauth2 = (props) => {
const startOauth2Request = (admin_consent) => {
// Admin consent also means to add refresh tokens
console.log("Inside oauth2 request for app: ", selectedApp.name)
selectedApp.name = selectedApp.name.replace(" ", "_").toLowerCase()
//console.log("APP: ", selectedApp)
if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") {
@@ -185,10 +196,10 @@ const AuthenticationOauth2 = (props) => {
)
} else if (selectedApp.name.toLowerCase() == "slack") {
handleOauth2Request(
"151779186901.2448678750935",
"5155508477298.5168162485601",
"",
"https://slack.com",
["chat:write:user", "im:read", "im:write", "search:read", "usergroups:read", "usergroups:write", "offline_access"],
["chat:write:user", "im:read", "im:write", "search:read", "usergroups:read", "usergroups:write",],
admin_consent,
)
} else if (selectedApp.name.toLowerCase() == "webex") {
@@ -196,7 +207,7 @@ const AuthenticationOauth2 = (props) => {
"Cab184f3d7271f540443c79b5b79845e3387abbbdb3db4233a87ea3a5432fb3d5",
"",
"https://webexapis.com",
["spark:all", "offline_access"],
["spark:all"],
admin_consent,
)
} else if (selectedApp.name.toLowerCase().includes("microsoft_teams")) {
@@ -212,7 +223,7 @@ const AuthenticationOauth2 = (props) => {
"35fa3a384040470db0c8527e90a3c2eb",
"",
"https://api.todoist.com",
["task:add", "offline_access"],
["task:add",],
admin_consent,
)
} else if (selectedApp.name.toLowerCase().includes("microsoft_sentinel")) {
@@ -220,7 +231,7 @@ const AuthenticationOauth2 = (props) => {
"4c16e8c4-3d34-4aa1-ac94-262ea170b7f7",
"",
"https://management.azure.com",
["https://management.azure.com/user_impersonation", "offline_access"],
["https://management.azure.com/user_impersonation",],
admin_consent,
)
} else if (selectedApp.name.toLowerCase().includes("microsoft_365_defender")) {
@@ -228,7 +239,7 @@ const AuthenticationOauth2 = (props) => {
"4c16e8c4-3d34-4aa1-ac94-262ea170b7f7",
"",
"https://graph.microsoft.com",
["SecurityEvents.ReadWrite.All", "offline_access"],
["SecurityEvents.ReadWrite.All",],
admin_consent,
)
} else if (selectedApp.name.toLowerCase().includes("google_sheets")) {
@@ -244,7 +255,7 @@ const AuthenticationOauth2 = (props) => {
handleOauth2Request(
"253565968129-6pij4g6ojim4gpum0h9m9u3bc357qsq7.apps.googleusercontent.com",
"",
"https://www.googleapis.com/drive/v3",
"https://www.googleapis.com",
["https://www.googleapis.com/auth/drive",],
admin_consent,
"consent",
@@ -254,9 +265,19 @@ const AuthenticationOauth2 = (props) => {
"AI02egeCQh1Zskm1QAJaaR6dzjR97V2F",
"",
"https://api.atlassian.com",
["read:jira-work", "write:jira-work", "read:servicedesk:jira-service-management", "write:servicedesk:jira-service-management", "read:request:jira-service-management", "write:request:jira-service-management", "offline_access"],
["read:jira-work", "write:jira-work", "read:servicedesk:jira-service-management", "write:servicedesk:jira-service-management", "read:request:jira-service-management", "write:request:jira-service-management",],
admin_consent,
)
} else if (selectedApp.name.toLowerCase().includes("github")) {
handleOauth2Request(
"3d272b1b782b100b1e61",
"",
"https://api.github.com",
["repo","user","project","notifications",],
admin_consent,
)
} else {
console.log("No match found for: ", selectedApp.name)
}
// write:request:jira-service-management
}
@@ -315,6 +336,7 @@ const AuthenticationOauth2 = (props) => {
}
var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`;
if (admin_consent === true) {
console.log("Running Oauth2 WITH admin consent")
//url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`;
@@ -605,7 +627,8 @@ const AuthenticationOauth2 = (props) => {
onClick={() => {
// Hardcode some stuff?
// This could prolly be added to the app itself with a "default" client ID
startOauth2Request(true)
//startOauth2Request(true)
startOauth2Request()
}}
color="primary"
>
+9 -1
View File
@@ -167,6 +167,7 @@ const ParsedAction = (props) => {
setLastSaved,
setShowVideo,
toolsAppId,
aiSubmit,
//expansionModalOpen,
//setExpansionModalOpen,
} = props;
@@ -1458,7 +1459,7 @@ const ParsedAction = (props) => {
if (found === null) {
setActivateHidingBodyButton(true);
} else {
console.log("In found: ", found, hideBody)
//console.log("In found: ", found, hideBody)
}
} else {
//console.log("SHOW BUTTON");
@@ -1532,6 +1533,12 @@ const ParsedAction = (props) => {
expansionModalOpen={expansionModalOpen}
setExpansionModalOpen={setExpansionModalOpen}
globalUrl={globalUrl}
workflowExecutions={workflowExecutions}
getParents={getParents}
selectedAction={selectedAction}
parameterName={data.name}
aiSubmit={aiSubmit}
/>
)
@@ -3222,6 +3229,7 @@ const ParsedAction = (props) => {
</Tooltip>
</div>
) : null}
{selectedAction.authentication !== undefined &&
selectedAction.authentication !== null &&
selectedAction.authentication.length > 0 ? (
+9 -5
View File
@@ -83,28 +83,32 @@ const SearchField = props => {
*/
return (
<form id="search_form" noValidate type="searchbox" action="" role="search" style={{margin: 0, }} onClick={() => {
<form id="search_form" noValidate type="searchbox" action="" role="search" style={{margin: "10px 0px 0px 0px", }} onClick={() => {
}}>
<TextField
fullWidth
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
style={{backgroundColor: theme.palette.surfaceColor, borderRadius: borderRadius, minWidth: 403, maxWidth: 403, }}
InputProps={{
style:{
color: "white",
fontSize: "1em",
height: 50,
margin: 0,
fontSize: "0.9em",
paddingLeft: 10,
},
startAdornment: (
disableUnderline: true,
endAdornment: (
<InputAdornment position="start">
<SearchIcon style={{marginLeft: 5}}/>
<SearchIcon style={{marginLeft: 5, color: "#f86a3e",}}/>
</InputAdornment>
),
}}
autoComplete='off'
type="search"
color="primary"
placeholder="Find Public Apps, Workflows, Documentation and more"
placeholder="Find Public Apps, Workflows, Documentation..."
value={currentRefinement}
id="shuffle_search_field"
onClick={(event) => {
+234 -8
View File
@@ -20,6 +20,7 @@ import { orange } from '@mui/material/colors';
import { isMobile } from "react-device-detect"
import NestedMenuItem from "material-ui-nested-menu-item";
import { GetParsedPaths, FindJsonPath } from "../views/Apps.jsx";
import { SetJsonDotnotation } from "../views/AngularWorkflow.jsx";
import {
FullscreenExit as FullscreenExitIcon,
@@ -114,7 +115,24 @@ const pythonFilters = [
//});
const CodeEditor = (props) => {
const { globalUrl, fieldCount, setFieldCount, actionlist, changeActionParameterCodeMirror, expansionModalOpen, setExpansionModalOpen, codedata, setcodedata, isFileEditor, runUpdateText, toolsAppId } = props
const {
globalUrl,
fieldCount,
setFieldCount,
actionlist,
changeActionParameterCodeMirror,
expansionModalOpen,
setExpansionModalOpen,
codedata,
setcodedata,
isFileEditor,
runUpdateText,
toolsAppId,
parameterName,
selectedAction ,
workflowExecutions,
getParents,
} = props
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
// const {codelang, setcodelang} = props
@@ -140,6 +158,8 @@ const CodeEditor = (props) => {
const [menuPosition, setMenuPosition] = useState(null);
const [showAutocomplete, setShowAutocomplete] = React.useState(false);
const [isAiLoading, setIsAiLoading] = React.useState(false);
const baseResult = ""
const [executionResult, setExecutionResult] = useState({
"valid": false,
@@ -188,8 +208,201 @@ const CodeEditor = (props) => {
setMainVariables(tmpVariables)
}, [])
const aiSubmit = (value, inputAction) => {
if (value === undefined || value === "") {
console.log("No value input!")
return
}
setIsAiLoading(true)
// Time to construct this huh... Hmm
var AppContext = []
if (inputAction !== undefined && inputAction !== null && getParents !== undefined && getParents !== null && workflowExecutions !== undefined && workflowExecutions !== null) {
const parents = getParents(inputAction)
console.log("Parents: ", parents)
var actionlist = []
if (parents.length > 1) {
for (let [key,keyval] in Object.entries(parents)) {
const item = parents[key];
if (item.label === "Execution Argument") {
continue;
}
var exampledata = item.example === undefined || item.example === null ? "" : item.example;
// Find previous execution and their variables
//exampledata === "" &&
if (workflowExecutions.length > 0) {
// Look for the ID
const found = false;
for (let [key,keyval] in Object.entries(workflowExecutions)) {
if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
continue;
}
var foundResult = workflowExecutions[key].results.find((result) => result.action.id === item.id);
if (foundResult === undefined || foundResult === null) {
continue;
}
if (foundResult.result !== undefined && foundResult.result !== null) {
foundResult = foundResult.result
}
const valid = validateJson(foundResult, true)
if (valid.valid) {
if (valid.result.success === false) {
//console.log("Skipping success false autocomplete")
} else {
exampledata = valid.result;
break;
}
} else {
exampledata = foundResult;
}
}
}
// 1. Take
const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_");
const actionvalue = {
app_name: item.app_name,
action_name: item.name,
label: item.label,
type: "action",
id: item.id,
name: item.label,
autocomplete: itemlabelComplete,
example: exampledata,
};
actionlist.push(actionvalue);
}
}
var fixedResults = []
for (var i = 0; i < actionlist.length; i++) {
const item = actionlist[i];
const responseFix = SetJsonDotnotation(item.example, "")
// Check if json
const validated = validateJson(responseFix)
var exampledata = responseFix;
if (validated.valid) {
exampledata = JSON.stringify(validated.result)
}
AppContext.push({
"app_name": item.app_name,
"action_name": item.action_name,
"label": item.label,
"example": exampledata,
})
}
}
var conversationData = {
"query": value,
"output_format": "action",
"app_context": AppContext,
}
if (inputAction !== undefined) {
console.log("Add app context! This should them get parameters directly")
conversationData.output_format = "action_parameters"
conversationData.app_id = inputAction.app_id
conversationData.app_name = inputAction.app_name
conversationData.action_name = inputAction.name
conversationData.parameters = inputAction.parameters
}
fetch(`${globalUrl}/api/v1/conversation`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(conversationData),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for stream results :O!");
}
return response.json();
})
.then((responseJson) => {
console.log("Conversation response: ", responseJson)
setIsAiLoading(false)
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
}
return
}
if (inputAction !== undefined) {
console.log("In input action! Should check params if they match, and add suggestions")
if (responseJson.parameters === undefined || responseJson.parameters.length === 0) {
return
}
for (let respParam of responseJson.parameters) {
if (respParam.name !== parameterName) {
continue
}
if (respParam.value === "") {
break
}
setlocalcodedata(respParam.value)
break
}
return
}
})
.catch((error) => {
setIsAiLoading(false)
console.log("Conv response error: ", error);
});
}
const autoFormat = (input) => {
// Check if it's default too
if (validation !== true) {
// Should try to automatically fix this input
console.log("Running AI input fixer")
if (aiSubmit !== undefined && parameterName !== undefined && selectedAction !== undefined) {
// Should remove params from selectedAction that aren't parameterName
var tmpAction = JSON.parse(JSON.stringify(selectedAction))
var tmpParams = selectedAction.parameters.filter((param) => param.name === parameterName)
var aiMsg = `Make it valid for action ${tmpAction.label} with parameter ${parameterName}: `
if (tmpParams.length > 0) {
aiMsg += tmpParams[0].value
}
if (localcodedata.startsWith("//")) {
aiMsg = localcodedata
}
tmpAction.parameters = tmpParams
console.log("Parameters: ", tmpParams.length)
aiSubmit(aiMsg, tmpAction)
}
return
}
@@ -295,6 +508,8 @@ const CodeEditor = (props) => {
var removedIndexes = 0
for (var key in itemsplit) {
var tmpitem = itemsplit[key]
// Makes sure #0 and # are same, as we only visualize first one anyway
if (tmpitem.startsWith("#")) {
removedIndexes += tmpitem.length-1
tmpitem = "#"
@@ -303,7 +518,7 @@ const CodeEditor = (props) => {
newitem.push(tmpitem)
}
//console.log("Fixed item: ", newitem, "removed length: ", removedIndexes)
console.log("Fixed item: ", newitem, "removed length: ", removedIndexes)
return newitem.join(".")
//return inputvariable
@@ -450,12 +665,17 @@ const CodeEditor = (props) => {
const found = input.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g)
console.log("FOUND: ", found)
// Whelp this is inefficient af. Single loop pls
// When the found array is empty.
if (found !== null && found !== undefined) {
try {
for (var i = 0; i < found.length; i++) {
try {
// For found specifically, should replace .#\d with .# with regex
//found[i] = found[i].toLowerCase()
const fixedVariable = fixVariable(found[i])
//var correctVariable = availableVariables.includes(fixedVariable)
@@ -468,15 +688,15 @@ const CodeEditor = (props) => {
try {
if (typeof actionlist[j].example === "object") {
input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example));
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
} else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example));
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
} else {
input = input.replace(fixedVariable, actionlist[j].example)
input = input.replace(found[i], actionlist[j].example, -1)
}
} catch (e) {
input = input.replace(fixedVariable, actionlist[j].example)
input = input.replace(found[i], actionlist[j].example, -1)
}
} else {
// Couldn't find the correct example value
@@ -523,7 +743,8 @@ const CodeEditor = (props) => {
}
//console.log("FOUND2: ", fixedVariable, actionlist[j].example)
input = input.replace(fixedVariable, new_input)
input = input.replace(fixedVariable, new_input, -1)
input = input.replace(found[i], new_input, -1)
//} catch (e) {
// input = input.replace(found[i], actionlist[k].example)
@@ -769,6 +990,7 @@ const CodeEditor = (props) => {
height: 50,
width: 50,
}}
disabled={isAiLoading}
onClick={() => {
autoFormat(localcodedata)
}}
@@ -778,7 +1000,11 @@ const CodeEditor = (props) => {
title={"Auto format data"}
placement="top"
>
<AutoFixHighIcon style={{color: "rgba(255,255,255,0.7)"}}/>
{isAiLoading ?
<CircularProgress style={{height: 20, width: 20, color: "rgba(255,255,255,0.7)"}}/>
:
<AutoFixHighIcon style={{color: "rgba(255,255,255,0.7)"}}/>
}
</Tooltip>
</IconButton>
</div>
+57 -36
View File
@@ -5,10 +5,11 @@ import { useNavigate, Link } from "react-router-dom";
import countries from "../components/Countries.jsx";
import CodeEditor from "../components/ShuffleCodeEditor.jsx";
import getLocalCodeData from "../components/ShuffleCodeEditor.jsx";
import CacheView from "../components/CacheView.jsx";
import theme from "../theme";
import AddIcon from "@mui/icons-material/Add";
import ClearIcon from '@mui/icons-material/Clear';
import StorageIcon from '@mui/icons-material/Storage';
//import ToggleButton from '@mui/material/ToggleButton';
import {
FormControl,
@@ -81,6 +82,7 @@ import Billing from "../components/Billing.jsx";
import Branding from "../components/Branding.jsx";
import Files from "../components/Files.jsx";
import { display, style } from "@mui/system";
//import EnvironmentStats from "../components/EnvironmentStats.jsx";
const useStyles = makeStyles({
notchedOutline: {
@@ -183,7 +185,7 @@ const Admin = (props) => {
const [billingInfo, setBillingInfo] = React.useState({});
useEffect(() => {
if (isDropzone) {
//redirectOpenApi();
@@ -695,13 +697,15 @@ const Admin = (props) => {
const handleGetOrg = (orgId) => {
if (orgId.length === 0) {
alert.error("Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.");
alert.error(
"Organization ID not defined. Please contact us on https://shuffler.io if this persists logout."
);
return;
}
// Just use this one?
const url = `${globalUrl}/api/v1/orgs/${orgId}`
fetch(url, {
fetch(`${globalUrl}/api/v1/orgs/${orgId}`, {
method: "GET",
credentials: "include",
headers: {
@@ -716,11 +720,7 @@ const Admin = (props) => {
})
.then((responseJson) => {
if (responseJson["success"] === false) {
if (responseJson.reason !== undefined) {
alert.error(responseJson.reason);
} else {
alert.error("Failed getting your org. If this persists, please contact support.");
}
alert.error("Failed getting your org. If this persists, please contact support.");
} else {
if (
responseJson.sync_features === undefined ||
@@ -1258,9 +1258,10 @@ const Admin = (props) => {
1: "users",
2: "app_auth",
3: "files",
4: "schedules",
5: "environments",
6: "suborgs",
4: "cache",
5: "schedules",
6: "environments",
7: "suborgs",
};
const admin_views = {
@@ -1268,6 +1269,7 @@ const Admin = (props) => {
1: "cloud_sync",
2: "billing",
3: "branding",
4: "cache",
};
const setConfig = (event, inputValue) => {
@@ -1281,21 +1283,25 @@ const Admin = (props) => {
document.title = "Shuffle - admin - app authentication";
getAppAuthentication();
} else if (newValue === 3) {
document.title = "Shuffle - admin - files";
document.title = "Shuffle - admin - Files";
} else if (newValue === 4) {
document.title = "Shuffle - admin - Datastore";
//listOrgCache("3fd181b9-fb29-41b7-b2f5-15292265d420");
} else if (newValue === 5) {
document.title = "Shuffle - admin - schedules";
getSchedules();
} else if (newValue === 5) {
} else if (newValue === 6) {
document.title = "Shuffle - admin - environments";
getEnvironments();
} else if (newValue === 6) {
} else if (newValue === 7) {
document.title = "Shuffle - admin - orgs";
getOrgs();
} else {
document.title = "Shuffle - admin";
}
if (newValue === 6) {
if (newValue === 8) {
console.log("Should get apps for categories.");
}
@@ -1394,7 +1400,8 @@ const Admin = (props) => {
if (!responseJson.success && responseJson.reason !== undefined) {
alert.error("Failed setting user: " + responseJson.reason);
} else {
alert.success("Set the user field " + field + " to " + value);
//alert.success("Set the user field " + field + " to " + value);
alert.success("Successfully updated user field " + field)
if (field !== "suborgs") {
setSelectedUserModalOpen(false);
@@ -2407,6 +2414,7 @@ const Admin = (props) => {
If not otherwise specified, Usage will reset monthly
</Typography>
<Grid container style={{ width: "100%", marginBottom: 15 }}>
{selectedOrganization.sync_features === undefined ||
selectedOrganization.sync_features === null
? null
@@ -2507,11 +2515,7 @@ const Admin = (props) => {
>
<DialogTitle>
<span style={{ color: "white" }}>
{curTab === 1
? "Add user"
: curTab === 6
? "Add Sub-Organization"
: "Add environment"}
{curTab === 1 ? "Add user" : curTab === 7 ? "Add Sub-Organization" : "Add environment"}
</span>
</DialogTitle>
<DialogContent>
@@ -2519,7 +2523,7 @@ const Admin = (props) => {
<Typography variant="body1" style={{ marginBottom: 10 }}>
We will send an email to invite them to your organization.
</Typography>
) : curTab === 6 ? (
) : curTab === 7 ? (
<Typography variant="body1" style={{ marginBottom: 10 }}>
The organization created will become a child of your current
organization, and be available to you.
@@ -2578,7 +2582,7 @@ const Admin = (props) => {
</span>
)}
</div>
) : curTab === 6 ? (
) : curTab === 7 ? (
<div>
Name
<TextField
@@ -2603,7 +2607,7 @@ const Admin = (props) => {
}}
/>
</div>
) : curTab === 5 ? (
) : curTab === 6 ? (
<div>
Environment Name
<TextField
@@ -2649,9 +2653,9 @@ const Admin = (props) => {
} else {
submitUser(modalUser);
}
} else if (curTab === 6) {
} else if (curTab === 7) {
createSubOrg(selectedOrganization.id, orgName);
} else if (curTab === 5) {
} else if (curTab === 6) {
submitEnvironment(modalUser);
}
}}
@@ -3002,7 +3006,7 @@ const Admin = (props) => {
/>
const schedulesView =
curTab === 4 ? (
curTab === 5 ? (
<div>
<div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Schedules</h2>
@@ -3108,7 +3112,7 @@ const Admin = (props) => {
) : null;
const appCategoryView =
curTab === 7 ? (
curTab === 8 ? (
<div>
<div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Categories</h2>
@@ -3434,7 +3438,7 @@ const Admin = (props) => {
) : null;
const environmentView =
curTab === 5 ? (
curTab === 6 ? (
<div>
<div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Environments</h2>
@@ -3697,11 +3701,12 @@ const Admin = (props) => {
);
})}
</List>
{/*<EnvironmentStats />*/}
</div>
) : null;
const organizationsTab =
curTab === 6 ? (
curTab === 7 ? (
<div>
<div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Organizations</h2>
@@ -3831,7 +3836,7 @@ const Admin = (props) => {
) : null;
const hybridTab =
curTab === 7 ? (
curTab === 8 ? (
<div>
<div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Hybrid</h2>
@@ -3882,6 +3887,16 @@ const Admin = (props) => {
</div>
) : null;
const cacheOrgView =
curTab === 4 ? (
<div>
<CacheView
globalUrl={globalUrl}
orgId = {selectedOrganization.id}
/>
</div>
) : null;
// primary={environment.Registered ? "true" : "false"}
const iconStyle = { marginRight: 10 };
@@ -3901,6 +3916,8 @@ const Admin = (props) => {
textColor="secondary"
onChange={setConfig}
aria-label="disabled tabs example"
variant="scrollable"
scrollButtons="auto"
>
<Tab
label=<span>
@@ -3927,6 +3944,11 @@ const Admin = (props) => {
Files
</span>
/>
<Tab
label=<span>
<StorageIcon style={iconStyle} /> Datastore
</span>
/>
<Tab
disabled={userdata.admin !== "true"}
label=<span>
@@ -3935,7 +3957,6 @@ const Admin = (props) => {
</span>
/>
<Tab
index={5}
disabled={userdata.admin !== "true"}
label=<span>
<EcoIcon style={iconStyle} />
@@ -3943,12 +3964,11 @@ const Admin = (props) => {
</span>
/>
<Tab
index={6}
value={6}
label=<span>
<BusinessIcon style={iconStyle} /> Tenants
</span>
/>
{/*window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><CloudIcon style={iconStyle} /> Hybrid</span>/> : null*/}
{/*window.location.protocol === "http:" && window.location.port === "3000" ? <Tab label=<span><LockIcon style={iconStyle} />Categories</span>/> : null*/}
</Tabs>
@@ -3969,6 +3989,7 @@ const Admin = (props) => {
{hybridTab}
{organizationsTab}
{appCategoryView}
{cacheOrgView}
</div>
</Paper>
</div>
+569 -12
View File
@@ -107,6 +107,7 @@ import {
VpnKey as VpnKeyIcon,
AddComment as AddCommentIcon,
Edit as EditIcon,
Send as SendIcon,
} from "@material-ui/icons";
import {
@@ -114,6 +115,7 @@ import {
ContentCopy as ContentCopyIcon,
Circle as CircleIcon,
SquareFoot as SquareFootIcon,
AutoFixHigh as AutoFixHighIcon,
} from '@mui/icons-material';
import Autocomplete from "@material-ui/lab/Autocomplete";
@@ -282,6 +284,51 @@ export function sortByKey(array, key) {
});
}
// look for keys and set values with shuffle dotnotation
// used primarily for AI autocompletions
export function SetJsonDotnotation(jsonInput, inputKey) {
if (jsonInput === undefined || jsonInput === null) {
return jsonInput;
}
// Check for array
if (Array.isArray(jsonInput)) {
for (var i = 0; i < jsonInput.length; i++) {
jsonInput[i] = SetJsonDotnotation(jsonInput[i], inputKey+".#");
}
return jsonInput;
// Check for dict
} else if (typeof jsonInput === "object") {
// Loop keys and values
for (var key in jsonInput) {
if (!jsonInput.hasOwnProperty(key)) {
continue
}
const value = jsonInput[key];
// Check if array
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
jsonInput[key][i] = SetJsonDotnotation(jsonInput[key][i], inputKey+"."+key+".#");
}
} else if (typeof value === "object") {
jsonInput[key] = SetJsonDotnotation(jsonInput[key], inputKey+"."+key);
} else {
jsonInput[key] = inputKey+"."+key
}
}
} else {
//jsonInput = inputKey
console.log("SetJsonDotnotation: jsonInput is not an object or array, but key ", jsonInput, typeof jsonInput);
}
return jsonInput;
}
export const green = "#86c142";
export const yellow = "#FECC00";
@@ -417,8 +464,7 @@ const AngularWorkflow = (defaultprops) => {
const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false);
const [showSkippedActions, setShowSkippedActions] = React.useState(false);
const [lastExecution, setLastExecution] = React.useState("");
const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] =
React.useState(false);
const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] = React.useState(false);
const curpath =
typeof window === "undefined" || window.location === undefined
@@ -486,6 +532,18 @@ const AngularWorkflow = (defaultprops) => {
const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0);
const [workflowRecommendations, setWorkflowRecommendations] = React.useState([]);
const [listCache, setListCache] = React.useState([]);
const [suggestionBox, setSuggestionBox] = React.useState({
"position": {
"top": 500,
"left": 500,
},
"open": false,
"attachedTo": "",
});
// This should all be set once, not on every iteration
// Use states and don't update lol
const cloudSyncEnabled =
@@ -565,6 +623,31 @@ const AngularWorkflow = (defaultprops) => {
}
}, [authenticationModalOpen])
const listOrgCache = (orgId) => {
fetch(`${globalUrl}/api/v1/orgs/${orgId}/list_cache`, {
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;
}
return response.json();
})
.then((responseJson) => {
setListCache(responseJson);
})
.catch((error) => {
alert.error(error.toString());
});
};
const getAvailableWorkflows = (trigger_index) => {
fetch(globalUrl + "/api/v1/workflows", {
method: "GET",
@@ -2718,10 +2801,60 @@ const AngularWorkflow = (defaultprops) => {
const onNodeSelect = (event, newAppAuth) => {
// Forces all states to update at the same time,
// Otherwise everything is SUPER slow
ReactDOM.unstable_batchedUpdates(() => {
ReactDOM.unstable_batchedUpdates(() => {
const data = event.target.data();
if (data.isButton) {
if (data.buttonType === "delete") {
if (data.buttonType === "suggestion") {
if (cy === undefined) {
console.log("Cy not defined yet")
return
}
// Inject HTML at a fixed location
//const newHtml = "<div id='suggestion' style='position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: white; z-index: 1000;'><div style='position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);'><h1>Do you want to add this suggestion?</h1><button id='suggestion_yes'>Yes</button><button id='suggestion_no'>No</button></div></div>"
// Find mouse cursor position on screen
console.log("Suggestion html to be added at location: ", event)
/*
const position = {
"top": cy.pan().y,
"left": cy.pan().x,
}
*/
const position = event.target.renderedPosition();
const container = cy.container();
const offset = {
left: container.offsetLeft,
top: container.offsetTop
};
// Calculate the actual screen position for the box
const screenPosition = {
left: position.x + offset.left - 150,
top: position.y + offset.top,
};
// Log the position to the console
console.log('Node screen position:', screenPosition);
const newbox = {
"position": screenPosition,
"node_position": event.target.position(),
"open": true,
"attachedTo": data.attachedTo,
}
console.log("Rendered position: ", newbox.node_position)
setSuggestionBox(newbox)
// Unselect
event.target.unselect();
} else if (data.buttonType === "delete") {
const parentNode = cy.getElementById(data.attachedTo);
if (parentNode !== null && parentNode !== undefined) {
removeNode(data.attachedTo)
@@ -3187,6 +3320,14 @@ const AngularWorkflow = (defaultprops) => {
console.log("DOne in the node update")
setSuggestionBox({
"position": {
"top": 500,
"left": 500,
},
"open": false,
"attachedTo": "",
});
sendStreamRequest({
"item": "node",
"type": "select",
@@ -3582,7 +3723,7 @@ const AngularWorkflow = (defaultprops) => {
}
const eventTarget = event.target.target()
console.log("BUTTON ADDED! Find parent from: ", eventTarget)
//console.log("BUTTON ADDED! Find parent from: ", eventTarget)
if (eventTarget.data("isButton") === true) {
const parentNode = cy.getElementById(eventTarget.data("attachedTo"))
event.target.remove()
@@ -4211,6 +4352,8 @@ const AngularWorkflow = (defaultprops) => {
item = newitem
item.type = "ACTION"
item.isStartNode = false
item.data.type = "ACTION"
item.data.isStartNode = false
}
item.data.id = uuidv4()
@@ -4473,7 +4616,46 @@ const AngularWorkflow = (defaultprops) => {
};
const addSuggestionButtons = (nodedata, event) => {
console.log("In suggestion buttons: ", nodedata, ". This is not enabled yet. Backend needs further work.")
//console.log("In suggestion buttons: ", nodedata, ". This is not enabled yet. Backend needs further work.")
// Skipping add for now. Should Re-enable
// Add a button for autocompletion based on input
console.log("Type: ", nodedata.type)
if (nodedata.type === "ACTION") {
const color = "#34a853"
// Fix icon
const iconInfo = {
icon: "M7.5 5.6 10 7 8.6 4.5 10 2 7.5 3.4 5 2l1.4 2.5L5 7zm12 9.8L17 14l1.4 2.5L17 19l2.5-1.4L22 19l-1.4-2.5L22 14zM22 2l-2.5 1.4L17 2l1.4 2.5L17 7l2.5-1.4L22 7l-1.4-2.5zm-7.63 5.29a.9959.9959 0 0 0-1.41 0L1.29 18.96c-.39.39-.39 1.02 0 1.41l2.34 2.34c.39.39 1.02.39 1.41 0L16.7 11.05c.39-.39.39-1.02 0-1.41l-2.33-2.35zm-1.03 5.49-2.12-2.12 2.44-2.44 2.12 2.12-2.44 2.44z",
iconColor: buttonColor,
iconBackgroundColor: buttonBackgroundColor,
};
const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
const decoratorNode = {
position: {
x: event.target.position().x + 0,
y: event.target.position().y + 65,
},
locked: true,
data: {
isButton: true,
isValid: true,
is_valid: true,
//label: "+",
attachedTo: nodedata.id,
imageColor: color,
buttonType: "suggestion",
icon: svgpin_Url,
iconBackground: iconInfo.iconBackgroundColor,
},
};
cy.add(decoratorNode);
}
return
//setWorkflowRecommendations(responseJson.actions)
@@ -7199,7 +7381,6 @@ const AngularWorkflow = (defaultprops) => {
setSelectedAction(newSelectedAction);
setUpdate(Math.random());
// FIXME - should change icon-node (descriptor) as well
const allNodes = cy.nodes().jsons();
if (allNodes !== undefined && allNodes !== null) {
for (let nodekey in allNodes) {
@@ -7221,7 +7402,12 @@ const AngularWorkflow = (defaultprops) => {
}
}
}
};
// Send it in here, after all fields are filled
// Disabled for now :(
// const aiMsg = "Fill based on previous values"
// aiSubmit(aiMsg, undefined, undefined, newSelectedAction)
};
// APPSELECT at top
// appname & version
@@ -10905,7 +11091,6 @@ const AngularWorkflow = (defaultprops) => {
fullWidth
multiline
rows="2"
defaultValue={trigger_header_auth}
color="primary"
disabled={selectedTrigger.status === "running"}
placeholder={"OK"}
@@ -12612,6 +12797,8 @@ const AngularWorkflow = (defaultprops) => {
requiresAuthentication={requiresAuthentication}
setLastSaved={setLastSaved}
lastSaved={lastSaved}
aiSubmit={aiSubmit}
/>
} else if (Object.getOwnPropertyNames(selectedComment).length > 0) {
@@ -12929,6 +13116,9 @@ const AngularWorkflow = (defaultprops) => {
getSettings();
getFiles()
// For loading datastore
// listOrgCache(workflow.org_id)
setUpdate(Math.random());
}}
>
@@ -14566,8 +14756,8 @@ const AngularWorkflow = (defaultprops) => {
<div style={{ display: "flex", marginBottom: 15 }}>
{curapp === null ? null : (
<img
alt={selectedResult.app_name}
src={curapp === undefined ? theme.palette.defaultImage : curapp.app_name === "shuffle-subflow" ? triggers[4].large_image : curapp.app_name === "User Input" ? triggers[5].large_image : curapp.large_image}
alt={selectedResult.action.app_name}
src={selectedResult === undefined ? theme.palette.defaultImage : selectedResult.action.app_name === "shuffle-subflow" ? triggers[4].large_image : selectedResult.action.app_name === "User Input" ? triggers[5].large_image : curapp.large_image}
style={{
marginRight: 20,
width: imgsize,
@@ -14596,6 +14786,15 @@ const AngularWorkflow = (defaultprops) => {
<div style={{ marginBottom: 5 }}>
<b>Status </b> {selectedResult.status}
</div>
<h1 onClick={() => {
console.log("APP: ", curapp)
console.log("Data: ", selectedResult)
}}>
HIYA: {selectedResult.action.app_name}
</h1>
{validate.valid ? (
<ReactJson
src={validate.result}
@@ -14613,8 +14812,12 @@ const AngularWorkflow = (defaultprops) => {
/>
) : (
<div>
<b>Result</b>&nbsp;
<b>Result</b>
<br/>
<span
style={{
wordBreak: "break-word",
}}
onClick={() => {
console.log("IN HERE TO CLICK");
to_be_copied = selectedResult.result;
@@ -15734,6 +15937,358 @@ const AngularWorkflow = (defaultprops) => {
</div>
</Dialog>
) : null;
// Should get AI autocompletes
const aiSubmit = (value, setResponseMsg, setSuggestionLoading, inputAction) => {
if (setResponseMsg !== undefined) {
setResponseMsg("")
}
if (value === undefined || value === "") {
console.log("No value input!")
return
}
if (setSuggestionLoading !== undefined) {
setSuggestionLoading(true)
}
console.log("Submit conversation with value: ", value);
// This is to find sample response and parse it as string
var AppContext = []
if (inputAction !== undefined && inputAction !== null) {
const parents = getParents(inputAction)
console.log("Parents: ", parents)
var actionlist = []
if (parents.length > 1) {
for (let [key,keyval] in Object.entries(parents)) {
const item = parents[key];
if (item.label === "Execution Argument") {
continue;
}
var exampledata = item.example === undefined || item.example === null ? "" : item.example;
// Find previous execution and their variables
//exampledata === "" &&
if (workflowExecutions.length > 0) {
// Look for the ID
const found = false;
for (let [key,keyval] in Object.entries(workflowExecutions)) {
if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
continue;
}
var foundResult = workflowExecutions[key].results.find((result) => result.action.id === item.id);
if (foundResult === undefined || foundResult === null) {
continue;
}
if (foundResult.result !== undefined && foundResult.result !== null) {
foundResult = foundResult.result
}
const valid = validateJson(foundResult, true)
if (valid.valid) {
if (valid.result.success === false) {
//console.log("Skipping success false autocomplete")
} else {
exampledata = valid.result;
break;
}
} else {
exampledata = foundResult;
}
}
}
// 1. Take
const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_");
const actionvalue = {
app_name: item.app_name,
action_name: item.name,
label: item.label,
type: "action",
id: item.id,
name: item.label,
autocomplete: itemlabelComplete,
example: exampledata,
};
actionlist.push(actionvalue);
}
}
var fixedResults = []
for (var i = 0; i < actionlist.length; i++) {
const item = actionlist[i];
const responseFix = SetJsonDotnotation(item.example, "")
// Check if json
const validated = validateJson(responseFix)
var exampledata = responseFix;
if (validated.valid) {
exampledata = JSON.stringify(validated.result)
}
AppContext.push({
"app_name": item.app_name,
"action_name": item.action_name,
"label": item.label,
"example": exampledata,
})
}
}
var conversationData = {
"query": value,
"output_format": "action",
"app_context": AppContext,
"workflow_id": workflow.id,
}
if (inputAction !== undefined) {
console.log("Add app context! This should them get parameters directly")
conversationData.output_format = "action_parameters"
conversationData.app_id = inputAction.app_id
conversationData.app_name = inputAction.app_name
conversationData.action_name = inputAction.name
conversationData.parameters = inputAction.parameters
if (!value.includes(inputAction.label)) {
conversationData.query = inputAction.label.replace("_", " ", -1)
}
}
// Onprem not available yet (April 2023)
// Should: Make OpenAI work for them with their own key
//fetch("https://shuffler.io/api/v1/conversation", {
fetch(`${globalUrl}/api/v1/conversation`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(conversationData),
credentials: "include",
})
.then((response) => {
if (setSuggestionLoading !== undefined) {
setSuggestionLoading(false)
}
if (response.status !== 200) {
console.log("Status not 200 for stream results :O!");
}
return response.json();
})
.then((responseJson) => {
console.log("Conversation response: ", responseJson)
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
if (setResponseMsg !== undefined) {
setResponseMsg(responseJson.reason)
}
}
return
}
if (inputAction !== undefined) {
console.log("In input action! Should check params if they match, and add suggestions")
if (responseJson.parameters === undefined || responseJson.parameters.length === 0) {
return
}
var changed = false
for (let paramkey in inputAction.parameters) {
const actionParam = inputAction.parameters[paramkey]
if (actionParam.autocompleted === true) {
continue
}
if (actionParam.configuration === true && actionParam.name !== "url") {
continue
}
if (actionParam.value !== "" && actionParam.value !== actionParam.example) {
console.log("Skipping: ", actionParam)
continue
}
for (let respParam of responseJson.parameters) {
if (respParam.name === actionParam.name) {
console.log("Found match for param: ", respParam)
if (respParam.value === "") {
break
}
changed = true
inputAction.parameters[paramkey].autocompleted = true
inputAction.parameters[paramkey].value = respParam.value
break
}
}
}
if (changed === true) {
console.log("Setting action! Force update pls :)")
setUpdate(Math.random())
setSelectedAction(inputAction)
}
return
}
console.log("Suggestionbox location: ", suggestionBox)
// Add action
if (responseJson.app_name !== undefined && responseJson.app_name !== null) {
// Always added to 0, 0
// Should use suggestionBox.position.x, suggestionBox.position.y
var newitem = {
"data": responseJson,
"position": {
"x": suggestionBox.node_position.x !== undefined ? suggestionBox.node_position.x : 0,
"y": suggestionBox.node_position.y !== undefined ? suggestionBox.node_position.y + 100 : 0,
},
"group": "nodes",
}
newitem.type = "ACTION"
newitem.isStartNode = false
newitem.data.id = uuidv4()
newitem.data.type = "ACTION"
newitem.data.isStartNode = false
newitem.data.is_valid = true
newitem.data.isValid = true
cy.add({
group: newitem.group,
data: newitem.data,
position: newitem.position,
});
// Add edge
const newId = uuidv4()
cy.add({
group: "edges",
data: {
id: newId,
_id: newId,
source: suggestionBox.attachedTo,
target: newitem.data.id,
}
})
//label: "Generated",
setSuggestionBox({
"position": {
"top": 500,
"left": 500,
},
"open": false,
"attachedTo": "",
});
}
})
.catch((error) => {
if (setSuggestionLoading !== undefined) {
setSuggestionLoading(false)
}
console.log("Conv response error: ", error);
});
}
const SuggestionBoxUi = () => {
const [suggestionValue, setSuggestionValue] = useState("");
const [suggestionLoading, setSuggestionLoading] = useState(false);
const [responseMsg, setResponseMsg] = useState("");
if (suggestionBox === undefined || suggestionBox.open === false) {
return false
}
return (
<div style={{width: 350, padding: 15, position: "fixed", top: suggestionBox.position.top, left: suggestionBox.position.left, borderRadius: theme.palette.borderRadius, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.3)", }}>
{/*
<AutoFixHighIcon style={{height: 12, width: 12, color: "white", position: "absolute", top: 10, right: 24, }} />
*/}
<Tooltip
title="Close"
placement="top"
style={{ zIndex: 10011 }}
>
<IconButton
style={{ zIndex: 5000, position: "absolute", top: 0, right: 0}}
onClick={(e) => {
e.preventDefault();
setSuggestionBox({
"position": {
"top": 500,
"left": 500,
},
"open": false,
"value": "",
"loading": false,
});
}}
>
<CloseIcon style={{ color: "white", height: 12, width: 12, }} />
</IconButton>
</Tooltip>
<form onSubmit={(e, value) => {
e.preventDefault();
aiSubmit(suggestionValue, setResponseMsg, setSuggestionLoading)
}}>
<TextField
id="suggestion-textfield"
style={{width: "90%"}}
disabled={suggestionLoading}
label="What action do you want to add?"
onChange={(e) => {
setSuggestionValue(e.target.value)
}}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Tooltip title="Run search" placement="top">
<SendIcon style={{ cursor: "pointer" }} onClick={(e) => {
e.preventDefault();
aiSubmit(suggestionValue, setResponseMsg, setSuggestionLoading)
}} />
</Tooltip>
</InputAdornment>
),
}}
/>
</form>
{suggestionLoading === true ?
<CircularProgress style={{height: 15, width: 15, marginTop: 5, }} />
: null}
{responseMsg.length > 0 ?
<Typography variant="body2">
{responseMsg}
</Typography>
: null}
</div>
)
}
const loadedCheck =
isLoaded && workflowDone ? (
<div>
@@ -15746,6 +16301,8 @@ const AngularWorkflow = (defaultprops) => {
{configureWorkflowModal}
{editWorkflowModal}
<SuggestionBoxUi />
{editWorkflowModalOpen === true ?
<EditWorkflow
workflow={workflow}
+100 -69
View File
@@ -300,7 +300,7 @@ const getJsonObject = (properties) => {
const jsonret = getJsonObject(property.items.properties);
if (property.type === "array") {
console.log("ARRAY!!")
//console.log("ARRAY!!")
jsonObject[key] = [jsonret];
} else {
jsonObject[key] = jsonret;
@@ -309,13 +309,13 @@ const getJsonObject = (properties) => {
if (property.hasOwnProperty("properties")) {
const jsonret = getJsonObject(property.properties);
if (property.type === "array") {
console.log("ARRAY2!!")
//console.log("ARRAY2!!")
jsonObject[key] = [jsonret];
} else {
jsonObject[key] = jsonret;
}
} else {
console.log("No items or properties found: ", property);
//console.log("No items or properties found: ", property);
}
}
@@ -426,9 +426,7 @@ const AppCreator = (defaultprops) => {
const [categories, setCategories] = useState(appCategories)
const isCloud =
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io";
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
useEffect(() => {
if (window.location.pathname.includes("apps/edit")) {
@@ -612,72 +610,103 @@ const AppCreator = (defaultprops) => {
setBasedata(data);
console.log("Info: ", data)
if (data.info !== null && data.info !== undefined) {
if (data.info.title !== undefined && data.info.title !== null) {
if (data.info.title.length > 29) {
setName(data.info.title.slice(0, 29));
} else {
setName(data.info.title);
}
}
setDescription(data.info.description);
document.title = "Apps - " + data.info.title;
try {
if (data.info !== null && data.info !== undefined) {
if (data.info.title !== undefined && data.info.title !== null) {
if (data.info.title.endsWith(" API")) {
data.info.title = data.info.title.substring(0, data.info.title.length - 4)
} else if (data.info.title.endsWith("API")) {
data.info.title = data.info.title.substring(0, data.info.title.length - 3)
}
if (data.info["x-logo"] !== undefined) {
if (data.info["x-logo"].url !== undefined) {
//console.log("PARSED LOGO: ", data.info["x-logo"].url);
setFileBase64(data.info["x-logo"].url);
} else {
setFileBase64(data.info["x-logo"]);
}
//console.log("");
//console.log("");
//console.log("LOGO: ", data.info["x-logo"]);
//console.log("");
//console.log("");
}
if (data.info.title.length > 29) {
setName(data.info.title.slice(0, 29));
} else {
setName(data.info.title);
}
}
if (data.info.contact !== undefined) {
setContact(data.info.contact);
}
setDescription(data.info.description);
document.title = "Apps - " + data.info.title;
if (data.info["x-categories"] !== undefined && data.info["x-categories"].length > 0) {
if (typeof data.info["x-categories"] === "array") {
} else {
}
setNewWorkflowCategories(data.info["x-categories"]);
}
}
if (data.info["x-logo"] !== undefined) {
if (data.info["x-logo"].url !== undefined) {
//console.log("PARSED LOGO: ", data.info["x-logo"].url);
setFileBase64(data.info["x-logo"].url);
} else {
setFileBase64(data.info["x-logo"]);
}
//console.log("");
//console.log("");
//console.log("LOGO: ", data.info["x-logo"]);
//console.log("");
//console.log("");
}
if (data.tags !== undefined && data.tags.length > 0) {
var newtags = [];
for (let tagkey in data.tags) {
if (data.tags[tagkey].name.length > 50) {
console.log("Skipping tag because it's too long: ",data.tags[tagkey].name.length);
if (data.info.contact !== undefined) {
setContact(data.info.contact);
}
continue;
}
if (data.info["x-categories"] !== undefined && data.info["x-categories"].length > 0) {
if (typeof data.info["x-categories"] === "array") {
} else {
}
setNewWorkflowCategories(data.info["x-categories"]);
}
}
} catch (e) {
console.log("Failed setting info: ", e)
}
newtags.push(data.tags[tagkey].name);
}
console.log("Tags: ", data.tags)
try {
if (data.tags !== undefined && data.tags.length > 0) {
var newtags = [];
for (let tagkey in data.tags) {
if (data.tags[tagkey].name.length > 50) {
console.log("Skipping tag because it's too long: ",data.tags[tagkey].name.length);
if (newtags.length > 10) {
newtags = newtags.slice(0, 9);
}
continue;
}
setNewWorkflowTags(newtags);
}
newtags.push(data.tags[tagkey].name);
}
if (newtags.length > 10) {
newtags = newtags.slice(0, 9);
}
setNewWorkflowTags(newtags);
}
} catch (e) {
console.log("Failed to parse tags: ", e)
}
// This is annoying (:
var securitySchemes = data.components.securityDefinitions;
if (securitySchemes === undefined) {
securitySchemes = data.securitySchemes;
}
console.log("Security schemes 1: ", data.securitySchemes)
if (securitySchemes === undefined) {
securitySchemes = data.components.securitySchemes;
}
// Weird generator problems to be handle
var securitySchemes = undefined
try {
if (data.securitySchemes !== undefined) {
securitySchemes = data.securitySchemes
if (securitySchemes === undefined) {
securitySchemes = data.securityDefinitions;
}
}
if (securitySchemes === undefined && data.components !== undefined) {
securitySchemes = data.components.securitySchemes;
if (securitySchemes === undefined) {
securitySchemes = data.components.securityDefinitions;
}
}
} catch (e) {
console.log("Failed to parse security schemes: ", e)
}
console.log("Security schemes 2: ", securitySchemes)
const allowedfunctions = [
"GET",
@@ -693,8 +722,9 @@ const AppCreator = (defaultprops) => {
var newActions = [];
var wordlist = {};
var all_categories = [];
console.log("Paths: ", data.paths)
var parentUrl = ""
console.log("Paths: ", data.paths)
if (data.paths !== null && data.paths !== undefined) {
for (let [path, pathvalue] of Object.entries(data.paths)) {
@@ -708,7 +738,7 @@ const AppCreator = (defaultprops) => {
// Typical YAML issue
if (method !== "parameters") {
console.log("Invalid method: ", method, "data: ", methodvalue);
alert.info("Skipped method (not allowed): " + method);
//alert.info("Skipped method (not allowed): " + method);
}
continue;
}
@@ -830,7 +860,7 @@ const AppCreator = (defaultprops) => {
if (methodvalue["requestBody"]["content"]["application/json"]["schema"]["properties"] !== undefined) {
// Read out properties from a JSON object
const jsonObject = getJsonObject(methodvalue["requestBody"]["content"]["application/json"]["schema"]["properties"])
console.log("JSON OBJECT: ", jsonObject)
//console.log("JSON OBJECT: ", jsonObject)
if (jsonObject !== undefined && jsonObject !== null) {
try {
newaction["body"] = JSON.stringify(jsonObject, null, 2)
@@ -1128,7 +1158,6 @@ const AppCreator = (defaultprops) => {
if (selectedExample["content"]["application/json"]["schema"]["properties"] !== undefined && selectedExample["content"]["application/json"]["schema"]["properties"] !== null) {
const jsonObject = getJsonObject(selectedExample["content"]["application/json"]["schema"]["properties"])
console.log("ReSP Return: ", jsonObject)
if (jsonObject !== undefined && jsonObject !== null) {
try {
newaction.example_response = JSON.stringify(jsonObject, null, 2)
@@ -1483,7 +1512,8 @@ const AppCreator = (defaultprops) => {
const searchactions = newActions.find(
(data) => data.name === newname
);
console.log("SEARCH: ", searchactions);
//console.log("SEARCH: ", searchactions);
if (searchactions !== undefined) {
newaction.errors.push("Missing name");
} else {
@@ -1532,13 +1562,13 @@ const AppCreator = (defaultprops) => {
}
if (securitySchemes !== undefined) {
// FIXME: Should add Oauth2 (Microsoft) and JWT (Wazuh)
//console.log("SECURITY: ", securitySchemes)
console.log("SECURITY: ", securitySchemes)
var newauth = [];
try {
var optionset = false
for (const [key, value] of Object.entries(securitySchemes)) {
console.log(key, value);
console.log("AUTH: ", key, value);
if (key === "jwt") {
setAuthenticationOption("JWT");
@@ -1749,7 +1779,8 @@ const AppCreator = (defaultprops) => {
if (!found) {
newActions2.push(action)
} else {
console.log("Skipping duplicate action: ", action.url, " . Should merge contents")
console.log("NOT skipping duplicate action: ", action.url, ". Should merge contents")
newActions2.push(action)
}
}
+2 -1
View File
@@ -112,7 +112,6 @@ const Docs = (defaultprops) => {
const SideBar = {
minWidth: 250,
maxWidth: 300,
borderRight: "1px solid rgba(255,255,255,0.3)",
left: 0,
position: "sticky",
top: 50,
@@ -121,6 +120,7 @@ const Docs = (defaultprops) => {
overflowX: "hidden",
overflowY: "auto",
zIndex: 1000,
//borderRight: "1px solid rgba(255,255,255,0.3)",
};
const fetchDocList = () => {
@@ -361,6 +361,7 @@ const Docs = (defaultprops) => {
maxWidth: "100%",
minWidth: "100%",
overflow: "hidden",
fontSize: isMobile ? "1.3rem" : "1.0rem",
};
function OuterLink(props) {
+45 -23
View File
@@ -5,6 +5,7 @@ import Stepper from "@material-ui/core/Stepper";
import Step from "@material-ui/core/Step";
import StepLabel from "@material-ui/core/StepLabel";
import AppFramework from "../components/AppFramework.jsx";
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import {
Grid,
Container,
@@ -303,19 +304,21 @@ const Welcome = (props) => {
}
const actionObject = {
padding: "50px 35px 50px 35px",
padding: "35px",
maxHeight: 300,
minHeight: 300,
}
const imageStyle = {
width: 150,
height: 150,
margin: "auto",
marginTop: 30,
// height: 150,
// margin: "auto",
// marginTop: 10,
borderRadius: 75,
objectFit: "scale-down",
}
const experienced_image = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : "/images/social/shuffle_logo_round.png"
const buttonStyle = { borderRadius: 8, height: 51, width: 464, fontSize: 16, background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)", padding: "16px 24px", top: 75, }
const experienced_image = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : "/images/experienced.png"
return (
<div style={{width: 1000, margin: "auto", backgroundColor: theme.palette.platformColor, paddingBottom: 150, minHeight: 1500, }}>
{/*
@@ -424,14 +427,26 @@ const Welcome = (props) => {
:
<Fade in={true}>
<div style={{maxWidth: 700, margin: "auto", marginTop: 50, }}>
<Typography variant="h4" style={{color: "white", textAlign: "center"}}>
Welcome to Shuffle
{/*
<div style={{display:"flex"}}>
<ArrowBackIosIcon style={{color: "#9E9E9E",}} onClick={() => {
navigate("/login")
}}/>
<Typography variant="body1" style={{color: "#9E9E9E",textAlign: "center", marginBottom: 50, paddingRight: "366px"}} onClick={() => {
navigate("/login")
}}>
Back
</Typography>
<Typography variant="body1" style={{textAlign: "center", marginBottom: 50, }}>
Who do you identify with the most?
</div>
*/}
<Typography variant="h4" style={{color: "#F1F1F1", textAlign: "center", paddingRight: "300px"}}>
Help us get to know you.
</Typography>
<Typography variant="body1" style={{color: "#9E9E9E", textAlign: "center", marginBottom: 50, paddingRight: "366px"}}>
Let us help you create a smoother journey.
</Typography>
<div style={{display: "flex", marginTop: 70, width: 700, margin: "auto",}}>
<div style={{border: "1px solid #49A928",}}>
<div style={{border: "1px solid #806BFF",}}>
<Card style={paperObject} onClick={() => {
if (isCloud) {
ReactGA.event({
@@ -446,20 +461,20 @@ const Welcome = (props) => {
setShowWelcome(true)
}}>
<CardActionArea style={actionObject}>
<Typography variant="h4" style={{color: "#49A928"}}>
<img src="/images/welcome-to-shuffle.png" style={imageStyle} />
<Typography variant="h4" style={{color: "#F1F1F1"}}>
New to Shuffle
</Typography>
<img src="/images/welcome_cog.png" style={imageStyle} />
<Typography variant="body1" style={{marginTop: 30, color: "rgba(255,255,255,0.8)"}}>
Follow our short introduction and learn some tips and tricks
<Typography variant="body1" style={{marginTop: 10, color: "rgba(255,255,255,0.8)"}}>
Let us guide you for an easier experience.
</Typography>
</CardActionArea>
</Card>
</div>
<div style={{marginLeft: 25, marginRight: 25, }}>
<Typography style={{marginTop: 200, }}>
{/* <Typography style={{marginTop: 200, }}>
OR
</Typography>
</Typography> */}
</div>
<Card style={paperObject} onClick={() => {
if (isCloud) {
@@ -473,17 +488,24 @@ const Welcome = (props) => {
navigate("/workflows?message=Skipped intro")
}}>
<CardActionArea style={actionObject}>
<Typography variant="h4" style={{color: "#f86a3e"}}>
<img src={experienced_image} style={{padding: "38px", objectFit: "scale-down", minHeight: 40, maxHeight: 40, }} />
<Typography variant="h4" style={{color: "#F1F1F1"}}>
Experienced
</Typography>
<img src={experienced_image} style={imageStyle} />
<Typography variant="body1" style={{marginTop: 30, color: "rgba(255,255,255,0.8)"}}>
You know Shuffle well. Head to your organization right away!
</Typography>
<Typography variant="body1" style={{marginTop: 10, color: "rgba(255,255,255,0.8)"}}>
You will head to Shuffle right away.
</Typography>
</CardActionArea>
</Card>
</div>
<div style={{display: "flex", flexDirection: "row", }}>
<Button variant="contained" type="submit" fullWidth style={buttonStyle} onClick={() => {
navigate("/workflows?message=Skipped intro")
}}>
Continue
</Button>
</div>
{/*
<div style={{margin: "auto", border: "1px solid rgba(255,255,255,0.5)", borderRadius: theme.palette.borderRadius, marginTop: 50, width: 200, overflow: "wrap", padding: 25, cursor: "pointer", }} onClick={() => {
if (window.drift !== undefined) {