Multiple frontend & backend fixes

This commit is contained in:
Frikky
2024-01-17 14:26:53 +01:00
parent 09ab560dd5
commit 14498f1fb4
10 changed files with 735 additions and 249 deletions
+47 -13
View File
@@ -73,6 +73,7 @@ import {
Visibility as VisibilityIcon,
VisibilityOff as VisibilityOffIcon,
Flag as FlagIcon,
FmdGood as FmdGoodIcon,
} from "@mui/icons-material";
@@ -1812,7 +1813,7 @@ If you're interested, please let me know a time that works for you, or set up a
{selectedAuthentication.label})
</span>
<Typography variant="body1" color="textSecondary" style={{marginTop: 10}}>
You can not see the previous values for an authentication while editing. This is to keep your data secure. You can overwrite one- or multiple fields at a time.
You can <b>not</b> see the previous values for an authentication while editing. This is to keep your data secure. You can overwrite one- or multiple fields at a time.
</Typography>
</DialogTitle>
<DialogContent>
@@ -1844,7 +1845,14 @@ If you're interested, please let me know a time that works for you, or set up a
/>
<Divider />
{selectedAuthentication.fields.map((data, index) => {
{selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app" ?
<div>
<Typography variant="body1" color="textSecondary" style={{ marginBottom: 0, marginTop: 10 }}>
Only the name of the auth can be modified for Oauth2. Please remake the authentication to change the fields like Client ID, Secret, Scopes etc.
</Typography>
</div>
:
selectedAuthentication.fields.map((data, index) => {
var fieldname = data.key.replaceAll("_", " ")
if (fieldname.endsWith(" basic")) {
fieldname = fieldname.substring(0, fieldname.length - 6)
@@ -1914,6 +1922,10 @@ If you're interested, please let me know a time that works for you, or set up a
}
}
if (selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app") {
selectedAuthentication.fields = []
}
if (error && fails === authenticationFields.length) {
toast("Updating auth with new name only")
saveAuthentication(selectedAuthentication);
@@ -2427,16 +2439,23 @@ If you're interested, please let me know a time that works for you, or set up a
</Dialog>
);
var regiontag = "eu";
if (userdata.region_url !== undefined && userdata.region_url !== null && userdata.region_url.length > 0) {
const regionsplit = userdata.region_url.split(".");
if (regionsplit.length > 2 && !regionsplit[0].includes("shuffler")) {
const namesplit = regionsplit[0].split("/");
regiontag = namesplit[namesplit.length - 1];
}
}
const organizationView =
curTab === 0 && selectedOrganization.id !== undefined ? (
<div style={{ position: "relative" }}>
<div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Organization overview</h2>
<span style={{ marginLeft: 25 }}>
On this page you can configure individual parts of your
organization.{" "}
<Typography variant="body1" color="textSecondary" style={{ marginLeft: 0}}>
On this page organization admins can configure organisations, and sub-orgs (MSSP).{" "}
<a
target="_blank"
rel="noopener noreferrer"
@@ -2445,7 +2464,7 @@ If you're interested, please let me know a time that works for you, or set up a
>
Learn more
</a>
</span>
</Typography>
</div>
{selectedOrganization.id === undefined ? (
<div
@@ -2518,6 +2537,21 @@ If you're interested, please let me know a time that works for you, or set up a
</FormControl>
</span>
: null}
{isCloud ?
<Tooltip
title={`Organization is in ${regiontag}. Click to change!`}
style={{}}
>
<Avatar
style={{ top: -10, right: 50, position: "absolute", }}
onClick={() => {
toast("Region change is not implemented yet for users. Please contact support.")
}}
>
{regiontag}
</Avatar>
</Tooltip>
: null}
<Tooltip
title={"Copy Organization ID"}
@@ -2597,13 +2631,13 @@ If you're interested, please let me know a time that works for you, or set up a
value={adminTab}
indicatorColor="primary"
textColor="secondary"
style={{marginTop: 20, }}
style={{marginTop: 50, }}
onChange={(event, inputValue) => {
const newValue = parseInt(inputValue);
const newValue = parseInt(inputValue);
setAdminTab(newValue);
//const setConfig = (event, inputValue) => {
navigate(`/admin?admin_tab=${admin_views[newValue]}`);
//const setConfig = (event, inputValue) => {
navigate(`/admin?admin_tab=${admin_views[newValue]}`);
}}
aria-label="disabled tabs example"
>
@@ -3846,7 +3880,7 @@ If you're interested, please let me know a time that works for you, or set up a
{data.defined ? (
<Tooltip
color="primary"
title="Set in EVERY workflow in the organization"
title="Set for EVERY instance of this App being used in this organization"
placement="top"
>
<IconButton
@@ -4289,7 +4323,7 @@ If you're interested, please let me know a time that works for you, or set up a
<div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Organizations</h2>
<span style={{ marginLeft: 25 }}>
Global admin: control organizations
Control sub organizations (tenants)! {isCloud ? "You can only make a sub organization if you are a customer of shuffle or running a POC of the platform. Please contact support@shuffler.io to try it out." : ""}. <a href="/docs/organizations" target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: theme.palette.primary.main }}>Learn more</a>
</span>
</div>
<Button
+327 -68
View File
@@ -128,8 +128,8 @@ import Draggable from "react-draggable";
import cytoscapestyle from "../defaultCytoscapeStyle.jsx";
import { validateJson, GetIconInfo } from "./Workflows.jsx";
import { GetParsedPaths } from "./Apps.jsx";
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import { GetParsedPaths, internalIds, } from "../views/Apps.jsx";
import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
import ParsedAction from "../components/ParsedAction.jsx";
@@ -523,6 +523,7 @@ const AngularWorkflow = (defaultprops) => {
const [_, setUpdate] = useState(""); // Used to force rendring, don't remove
const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
const [workflowExecutionCount, setWorkflowExecutionCount] = React.useState(0);
const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0);
const [workflowRecommendations, setWorkflowRecommendations] = React.useState(undefined);
@@ -547,6 +548,8 @@ const AngularWorkflow = (defaultprops) => {
"field_id": "",
})
const [executionArgumentModalOpen, setExecutionArgumentModalOpen] = React.useState(false);
// This should all be set once, not on every iteration
// Use states and don't update lol
const cloudSyncEnabled =
@@ -709,6 +712,33 @@ const AngularWorkflow = (defaultprops) => {
});
};
const getWorkflowExecutionCount = (workflowId) => {
fetch(`${globalUrl}/api/v1/workflows/${workflowId}/executions/count`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflow execution count: O!");
return;
} else {
return response.json();
}
})
.then((responseJson) => {
if (responseJson !== undefined) {
setWorkflowExecutionCount(responseJson.count || 0);
}
})
.catch((error) => {
toast(error.toString());
});
};
const getAvailableWorkflows = (trigger_index) => {
fetch(globalUrl + "/api/v1/workflows", {
method: "GET",
@@ -992,6 +1022,7 @@ const AngularWorkflow = (defaultprops) => {
})
.then((responseJson) => {
console.log("GOT A RESPONSE??")
getWorkflowExecutionCount(id);
if (responseJson !== undefined && responseJson !== null && responseJson.executions !== undefined && responseJson.executions !== null) {
// - means it's opposite
@@ -1733,6 +1764,7 @@ const AngularWorkflow = (defaultprops) => {
};
const executeWorkflow = (executionArgument, startNode, hasSaved) => {
ReactDOM.unstable_batchedUpdates(() => {
if (hasSaved === false) {
setExecutionRequestStarted(true);
@@ -1755,6 +1787,41 @@ const AngularWorkflow = (defaultprops) => {
setExecutionRequest({})
stop()
// FIXME: Check if any node contains $exec in a param
// If they do, show a popup asking if they want to execute it without an execution argument, or to use a previous one
if (executionArgument === undefined || executionArgument === null || executionArgument.length === 0 && workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
var foundmissing = false
for (let actionkey in workflow.actions) {
if (workflow.actions[actionkey].parameters === undefined || workflow.actions[actionkey].parameters === null || workflow.actions[actionkey].parameters.length === 0) {
continue
}
for (let paramkey in workflow.actions[actionkey].parameters) {
const param = workflow.actions[actionkey].parameters[paramkey]
if (param.value === undefined || param.value === null || param.value.length === 0) {
continue
}
if (param.value.indexOf("$exec") !== -1) {
foundmissing = true
break
}
}
if (foundmissing) {
break
}
}
console.log("FOUNDMISSING: ", foundmissing)
if (foundmissing) {
//toast("This workflow contains a node that requires an execution argument. Please provide one.")
setExecutionRequestStarted(false)
setExecutionArgumentModalOpen(true)
return
}
}
var curelements = cy.elements();
for (let i = 0; i < curelements.length; i++) {
curelements[i].addClass("not-executing-highlight");
@@ -1824,7 +1891,6 @@ const AngularWorkflow = (defaultprops) => {
setExecutionModalOpen(true);
setExecutionModalView(1);
start();
navigate(`/workflows/${props.match.params.key}?execution_id=${responseJson.execution_id}`)
})
.catch((error) => {
//toast(error.toString());
@@ -1837,7 +1903,6 @@ const AngularWorkflow = (defaultprops) => {
// This can be used to only show prioritzed ones later
// Right now, it can prioritize authenticated ones
//"Testing",
const internalIds = ["Shuffle Tools", "http", "email"];
const getAppAuthentication = (reset, updateAction, closeMenu) => {
fetch(globalUrl + "/api/v1/apps/authentication", {
@@ -2039,8 +2104,8 @@ const AngularWorkflow = (defaultprops) => {
setApps(responseJson);
if (isCloud) {
setFilteredApps(responseJson.filter((app) => !internalIds.includes(app.name)));
setPrioritizedApps(responseJson.filter((app) => internalIds.includes(app.name)));
setFilteredApps(responseJson.filter((app) => !internalIds.includes(app.name.toLowerCase())));
setPrioritizedApps(responseJson.filter((app) => internalIds.includes(app.name.toLowerCase())));
} else {
//setFilteredApps(
@@ -2051,12 +2116,12 @@ const AngularWorkflow = (defaultprops) => {
// )
//);
var tmpFiltered = responseJson.filter((app) => !internalIds.includes(app.name))
var tmpFiltered = responseJson.filter((app) => !internalIds.includes(app.name.toLowerCase()))
//tmpFiltered = sortByKey(tmpFiltered, "activated")
setFilteredApps(tmpFiltered)
//!(!app.activated && app.generated)
setPrioritizedApps(responseJson.filter((app) => internalIds.includes(app.name)));
setPrioritizedApps(responseJson.filter((app) => internalIds.includes(app.name.toLowerCase())));
}
setAppsLoaded(true)
@@ -3785,19 +3850,19 @@ const AngularWorkflow = (defaultprops) => {
return;
} else if (data.isDescriptor) {
console.log("Can't select descriptor");
if (data.isTrigger) {
console.log("But maybe we can select trigger descriptor? Maybe open execution tab?")
setExecutionModalOpen(true)
}
if (data.isTrigger) {
console.log("But maybe we can select trigger descriptor? Maybe open execution tab?")
setExecutionModalOpen(true)
}
event.target.unselect();
return;
}
if (data.type === undefined) {
console.log("No type, automatically setting to action");
data.type = "ACTION"
}
if (data.type === undefined) {
console.log("No type, automatically setting to action");
data.type = "ACTION"
}
if (data.type === "ACTION") {
setSelectedComment({})
@@ -3924,39 +3989,60 @@ const AngularWorkflow = (defaultprops) => {
const tmpAuth = JSON.parse(JSON.stringify(newAppAuth));
//console.log("FOUND AUTH OPTIONS: ", tmpAuth)
const curappName = curapp.name.toLowerCase()
for (let tmpAuthKey in tmpAuth) {
var item = tmpAuth[tmpAuthKey];
const curappName = curapp.name.toLowerCase()
for (let tmpAuthKey in tmpAuth) {
var item = tmpAuth[tmpAuthKey];
const newfields = {};
if (item.app.name.toLowerCase() !== curappName) {
continue
}
const newfields = {};
if (item.app.name.toLowerCase() !== curappName) {
continue
}
// Makes list into key:value object
for (let fieldkey in item.fields) {
if (item.fields[fieldkey] === undefined) {
console.log("Problem with filterkey in Node select", fieldkey)
continue
}
// Makes list into key:value object
for (let fieldkey in item.fields) {
if (item.fields[fieldkey] === undefined) {
console.log("Problem with filterkey in Node select", fieldkey)
continue
}
const filterkey = item.fields[fieldkey]["key"]
if (filterkey === null || filterkey === undefined) {
console.log("Problem with filterkey 2. Null or undefined 3")
continue
}
const filterkey = item.fields[fieldkey]["key"]
if (filterkey === null || filterkey === undefined) {
console.log("Problem with filterkey 2. Null or undefined 3")
continue
}
newfields[filterkey] = item.fields[fieldkey]["value"];
}
newfields[filterkey] = item.fields[fieldkey]["value"];
}
item.fields = newfields;
if (item.app.name.toLowerCase() === curappName) {
authenticationOptions.push(item);
if (item.id === findAuthId) {
curaction.selectedAuthentication = item;
}
}
}
item.fields = newfields;
if (item.app.name.toLowerCase() === curappName) {
authenticationOptions.push(item);
if (item.id === findAuthId) {
curaction.selectedAuthentication = item;
}
}
}
console.log("OPTIONS: ", authenticationOptions)
// Find with authenticationOption (authenticationOptions) has the highest .edited time. In this index, set the "last_modified" to true
var latesttime = 0
var latestindex = -1
for (var i = 0; i < authenticationOptions.length; i++) {
const authopt = authenticationOptions[i]
console.log("AUTHOPT: ", authopt)
if (authopt.edited > latesttime) {
latesttime = authopt.edited
latestindex = i
}
}
console.log("LATEST INDEX: ", latestindex)
if (latestindex !== -1) {
authenticationOptions[latestindex].last_modified = true
}
curaction.authentication = authenticationOptions;
if (
@@ -7468,26 +7554,26 @@ const AngularWorkflow = (defaultprops) => {
const tmpAuth = JSON.parse(JSON.stringify(appAuthentication));
for (let authkey in tmpAuth) {
if (authkey === undefined) {
continue
}
if (authkey === undefined) {
continue
}
var item = tmpAuth[authkey];
const newfields = {};
for (let fieldkey in item.fields) {
if (item.fields[fieldkey] === undefined) {
console.log("Problem with filterkey in Node select", fieldkey)
continue
}
for (let fieldkey in item.fields) {
if (item.fields[fieldkey] === undefined) {
console.log("Problem with filterkey in Node select", fieldkey)
continue
}
const filterkey = item.fields[fieldkey]["key"]
if (filterkey === null || filterkey === undefined) {
console.log("Problem with filterkey 2. Null or undefined 3")
continue
}
const filterkey = item.fields[fieldkey]["key"]
if (filterkey === null || filterkey === undefined) {
console.log("Problem with filterkey 2. Null or undefined 3")
continue
}
newfields[filterkey] = item.fields[fieldkey]["value"];
}
newfields[filterkey] = item.fields[fieldkey]["value"];
}
item.fields = newfields;
if (item.app.id === app.id || item.app.name === app.name) {
@@ -7734,9 +7820,9 @@ const AngularWorkflow = (defaultprops) => {
const [visibleApps, setVisibleApps] = React.useState(
Array.prototype.concat.apply(
prioritizedApps,
filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id)),
)
);
filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id.toLowerCase()))
)
)
var delay = -75
var runDelay = false
@@ -7983,7 +8069,7 @@ const AngularWorkflow = (defaultprops) => {
setVisibleApps(
prioritizedApps.concat(
filteredApps.filter(
(innerapp) => !internalIds.includes(innerapp.id)
(innerapp) => !internalIds.includes(innerapp.id.toLowerCase())
)
)
);
@@ -9159,6 +9245,133 @@ const AngularWorkflow = (defaultprops) => {
backgroundColor: theme.palette.inputColor,
};
// Makes a list of items the user can choose from based on previous runs
var availableArguments = []
if (executionArgumentModalOpen && workflowExecutions.length > 0) {
for (let executionKey in workflowExecutions) {
if (availableArguments.length > 5) {
break
}
const execution = workflowExecutions[executionKey]
if (execution.execution_argument === undefined || execution.execution_argument === null || execution.execution_argument.length < 2) {
continue
}
if (availableArguments.includes(execution.execution_argument)) {
continue
}
availableArguments.push(execution.execution_argument)
}
}
const executionArgumentModal =
<Dialog
PaperComponent={PaperComponent}
disableEnforceFocus={true}
hideBackdrop={true}
disableBackdropClick={true}
style={{ pointerEvents: "none" }}
PaperComponent={PaperComponent}
aria-labelledby="draggable-dialog-title"
open={executionArgumentModalOpen}
PaperProps={{
style: {
padding: 30,
pointerEvents: "auto",
color: "white",
minWidth: isMobile ? "90%" : 800,
border: theme.palette.defaultBorder,
},
}}
onClose={() => {
}}
>
<Tooltip
title="Close window"
placement="top"
style={{ zIndex: 10011 }}
>
<IconButton
style={{ zIndex: 5000, position: "absolute", top: 34, right: 34 }}
onClick={(e) => {
e.preventDefault();
setExecutionArgumentModalOpen(false);
}}
>
<CloseIcon style={{ color: "white" }} />
</IconButton>
</Tooltip>
<DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
<span style={{ color: "white" }}>Please provide an execution argument</span>
</DialogTitle>
<DialogContent>
<Typography variant="body1" color="textSecondary">
At least one node in this workflow requires an execution argument. Please select one below, or provide a custom one in the text field next to the run button.
</Typography>
{/*
<div style={{marginTop: 10, }}>
<Tooltip
color="primary"
title="An argument to be used for execution. This is a variable available to every node in your workflow."
placement="top"
>
<TextField
id="execution_argument_input_field"
style={theme.palette.textFieldStyle}
disabled={workflow.public}
color="secondary"
placeholder={"Execution Argument"}
defaultValue={executionText}
onBlur={(e) => {
setExecutionText(e.target.value);
}}
/>
</Tooltip>
</div>
*/}
<Divider style={{marginTop: 10, marginBottom: 20, }}/>
{availableArguments.length > 0 ?
<div>
<Typography variant="body1" style={{}}>
Previously used arguments:
</Typography>
{availableArguments.map((data) => {
return (
<Paper style={{ padding: 10, marginTop: 10, backgroundColor: theme.palette.platformColor, maxHeight: 70, overflow: "auto", cursor: "pointer", position: "relative", }}
onClick={() => {
setExecutionText(data)
executeWorkflow(data, workflow.start, lastSaved);
setExecutionArgumentModalOpen(false)
}}
>
<div style={{height: "100%", width: 2, backgroundColor: "rgba(255, 255, 255, 0.5)", position: "absolute", left: 0, top: 0}} />
<Typography variant="body1" color="textSecondary">
{data}
</Typography>
</Paper>
)
})}
</div>
: null}
<Button
variant="outlined"
color="primary"
onClick={() => {
executeWorkflow(" ", workflow.start, lastSaved);
setExecutionArgumentModalOpen(false);
}}
style={{ marginTop: 20, marginBottom: 20 }}
>
Run anyway
</Button>
</DialogContent>
</Dialog>
const aiQueryModal =
<Dialog
PaperComponent={PaperComponent}
@@ -13490,9 +13703,54 @@ const AngularWorkflow = (defaultprops) => {
variant="body2"
>
{workflow.errors.slice(0,3).map((error) => {
// Loop through each word, and if it matches "Action <name> " then replace it with a link to the action
var colornext = false
const newerror = error === undefined || error == null ? "" : error.split(" ").map((word) => {
if (colornext) {
colornext = false
return (
<span
style={{color: "#f85a3e", cursor: "pointer"}}
onClick={() => {
console.log("Clicked action: ", word)
// Find it in cytoscape
if (cy === undefined || cy === null) {
return
}
const foundnode = cy.nodes().filter((node) => {
return node.data().label === word
})
if (foundnode === undefined || foundnode === null || foundnode.length === 0) {
return
}
console.log("Found node: ", foundnode)
cy.elements().unselect()
foundnode[0].select()
}}
>
{word}&nbsp;
</span>
)
}
if (word.toLowerCase() === "action") {
colornext = true
}
return word + " "
})
if (newerror === undefined || newerror === null || newerror === "") {
return null
}
return (
<div>
- {error}
- {newerror}
</div>
)
})}
@@ -13635,7 +13893,7 @@ const AngularWorkflow = (defaultprops) => {
</Button>
</span>
</Tooltip>
{workflow.public ? (
{workflow.public || userdata.support == true ? (
<Tooltip
color="secondary"
title="Download workflow"
@@ -14811,7 +15069,7 @@ const AngularWorkflow = (defaultprops) => {
>
<h2 style={{ color: "rgba(255,255,255,0.5)" }}>
<DirectionsRunIcon style={{ marginRight: 10 }} />
All Workflow Runs
All Workflow Runs: { workflowExecutionCount }
</h2>
</Breadcrumbs>
<Tooltip
@@ -14819,7 +15077,7 @@ const AngularWorkflow = (defaultprops) => {
placement="left-start"
style={{ zIndex: 10010 }}
>
<a href={`/workflows/debug?workflow_id=${workflow.id}`} style={{textDecoration: "none", }}>
<a target="_blank" href={`/workflows/debug?workflow_id=${workflow.id}`} style={{textDecoration: "none", }}>
<Button
color="secondary"
style={{marginLeft: 50, maxHeight: 30, marginTop: 20, }}
@@ -14839,7 +15097,7 @@ const AngularWorkflow = (defaultprops) => {
color="primary"
>
<CachedIcon style={{ marginRight: 10 }} />
Refresh Runs
Refresh Runs
</Button>
<Divider
style={{
@@ -17910,6 +18168,7 @@ const AngularWorkflow = (defaultprops) => {
{newView}
<VariablesModal variableInfo={variableInfo} setVariableInfo={setVariableInfo} />
<ExecutionVariableModal variableInfo={variableInfo} setVariableInfo={setVariableInfo} />
{executionArgumentModal}
{aiQueryModal}
{conditionsModal}
{authenticationModal}
+11 -5
View File
@@ -1527,6 +1527,10 @@ const AppCreator = (defaultprops) => {
in: "query",
};
if (parameter.example !== undefined && parameter.example !== null) {
tmpaction.example = parameter.example
}
if (parameter.required === undefined) {
tmpaction.required = false;
}
@@ -2289,7 +2293,7 @@ const AppCreator = (defaultprops) => {
}
const methodname = item.method.toLowerCase()
if (methodname === "post" || methodname === "put" || methodname === "patch") {
if (methodname === "post" || methodname === "put" || methodname === "patch" || methodname === "delete") {
if (
item.body !== undefined &&
item.body !== null &&
@@ -3432,11 +3436,11 @@ const AppCreator = (defaultprops) => {
};
const deletePathQuery = (index) => {
console.log("Should delete index: ", index)
var tmpqueries = JSON.parse(JSON.stringify(urlPathQueries))
tmpqueries.splice(index, 1)
console.log("Should delete index: ", index)
var tmpqueries = JSON.parse(JSON.stringify(urlPathQueries))
tmpqueries.splice(index, 1)
console.log("Queries: ", tmpqueries)
console.log("Queries: ", tmpqueries)
setUrlPathQueries(tmpqueries);
if (updater === "deleteupdater") {
@@ -4316,6 +4320,8 @@ const AppCreator = (defaultprops) => {
setCurrentAction(data);
setCurrentActionMethod(data.method);
console.log("QUERIES: ", data.queries)
setUrlPathQueries(data.queries);
setUrlPath(data.url);
setActionsModalOpen(true);
+204 -58
View File
@@ -88,6 +88,7 @@ export const FixName = (name) => {
return newAppname;
};
// Takes input of e.g. $node.data.#.asd and a matching value from a json blob
// Returns
export const FindJsonPath = (path, inputdata) => {
@@ -157,6 +158,8 @@ export const FindJsonPath = (path, inputdata) => {
return inputdata
}
export const internalIds = ["shuffle tools", "http", "email"];
// Parses JSON data into keys that can be used everywhere :)
// Reverse of this is FindJsonPath
export const GetParsedPaths = (inputdata, basekey) => {
@@ -270,7 +273,7 @@ export const GetParsedPaths = (inputdata, basekey) => {
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
const Apps = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
const { globalUrl, isLoggedIn, isLoaded, userdata, serverside, } = props;
//const [workflows, setWorkflows] = React.useState([]);
const baseRepository = "https://github.com/frikky/shuffle-apps";
@@ -304,6 +307,8 @@ const Apps = (props) => {
const [cursearch, setCursearch] = React.useState("");
const [sharingConfiguration, setSharingConfiguration] = React.useState("you");
const [downloadBranch, setDownloadBranch] = React.useState("master");
const [creatorProfile, setCreatorProfile] = React.useState({});
const [contact, setContact] = React.useState("");
const [isDropzone, setIsDropzone] = React.useState(false);
const upload = React.useRef(null);
@@ -324,6 +329,40 @@ const Apps = (props) => {
},
});
const getUserProfile = (username) => {
if (serverside === true || !isCloud) {
setCreatorProfile({})
return;
}
fetch(`${globalUrl}/api/v1/users/creators/${username}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success !== false) {
setCreatorProfile(responseJson);
} else {
setCreatorProfile({})
}
})
.catch((error) => {
console.log(error);
setCreatorProfile({})
});
};
useEffect(() => {
if (apps.length <= 0 && firstrequest) {
document.title = "Shuffle - Apps";
@@ -441,6 +480,12 @@ const Apps = (props) => {
setFilteredApps(privateapps);
if (privateapps.length > 0) {
if (selectedApp.id === undefined || selectedApp.id === null) {
if (privateapps[0].owner !== undefined && privateapps[0].owner !== null) {
getUserProfile(privateapps[0].owner);
}
setContact(privateapps[0].contact_info)
setSelectedApp(privateapps[0]);
setSharingConfiguration(privateapps[0].sharing === true ? "public" : "you")
}
@@ -646,6 +691,13 @@ const Apps = (props) => {
style={paperAppStyle}
onClick={() => {
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")
@@ -882,7 +934,7 @@ const Apps = (props) => {
Activate App
</Button>
</Link>
<Tooltip title={"Delete app"}>
<Tooltip title={"Delete app (confirm box will show)"}>
<Button
variant="outlined"
component="label"
@@ -1114,7 +1166,7 @@ const Apps = (props) => {
{activateButton}
{ /* editNewButton === null && */ }
{ /* editNewButton === null && */ }
{canEditApp ? (
<div>
@@ -1127,35 +1179,12 @@ const Apps = (props) => {
{editNewButton}
</div>
}
{selectedApp.tags !== undefined && selectedApp.tags !== null ? (
<div
style={{
display: "inline-block",
marginLeft: 15,
float: "right",
}}
>
{selectedApp.tags.map((tag, index) => {
if (index >= 3) {
return null;
}
return (
<Chip
key={index}
style={chipStyle}
variant="outlined"
label={tag}
color="primary"
/>
);
})}
</div>
) : null}
{canEditApp
? (
<div style={{ marginTop: 15 }}>
{canEditApp
? (
<div style={{ marginTop: 15, display: "flex", }}>
{/*<p><b>ID:</b> {selectedApp.id}</p>*/}
<b style={{ marginRight: 15 }}>Sharing </b>
<Select
value={sharingConfiguration}
@@ -1209,8 +1238,117 @@ const Apps = (props) => {
);
})}
</Select>
{isCloud && (selectedApp.sharing === true || selectedApp.public === true || creatorProfile.github_avatar !== undefined) && !internalIds.includes(selectedApp.name.toLowerCase()) ?
<Button
variant="contained"
component="label"
color="primary"
onClick={() => {
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: "db08e40265e2941b9a7d8f644b6e5240",
});
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)
}}
style={{ height: 35, marginTop: 0, marginLeft: 10, }}
>
Deactivate
</Button>
: null}
</div>
) : null}
<div style={{display: "flex", }}>
{isCloud && Object.getOwnPropertyNames(creatorProfile).length !== 0 && creatorProfile.github_avatar !== undefined && creatorProfile.github_avatar !== null ?
<div style={{ display: "flex", marginTop: 15, }}>
<IconButton
color="primary"
style={{ padding: 0, marginRight: 10 }}
aria-controls="simple-menu"
aria-haspopup="true"
onClick={(event) => {
//setAnchorElAvatar(event.currentTarget);
}}
>
<Link
to={`/creators/${creatorProfile.github_username}`}
style={{ textDecoration: "none", color: "#f86a3e" }}
>
<Avatar
style={{ height: 30, width: 30 }}
alt={contact.name}
src={creatorProfile.github_avatar}
/>
</Link>
</IconButton>
<Typography
variant="body1"
color="textSecondary"
style={{ color: "" }}
>
Shared by{" "}
<Link
to={`/creators/${creatorProfile.github_username}`}
style={{ textDecoration: "none", color: "#f86a3e" }}
>
{creatorProfile.github_username}
</Link>
</Typography>
</div>
: null}
{selectedApp.tags !== undefined && selectedApp.tags !== null ? (
<div
style={{
marginLeft: 20,
marginTop: 15,
}}
>
{selectedApp.tags.map((tag, index) => {
if (index >= 3) {
return null;
}
return (
<Chip
key={index}
style={chipStyle}
variant="outlined"
label={tag}
color="primary"
/>
);
})}
</div>
) : null}
</div>
{/*<p><b>Owner:</b> {selectedApp.owner}</p>*/}
{selectedApp.privateId !== undefined &&
selectedApp.privateId.length > 0 ? (
@@ -1666,37 +1804,45 @@ const Apps = (props) => {
)
}
const activateApp = (appid, refresh) => {
fetch(globalUrl + "/api/v1/apps/" + appid + "/activate", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
const activateApp = (appid, refresh) => {
const appExists = userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appid)
const url = 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((response) => {
if (response.status !== 200) {
console.log("Failed to activate")
}
.then((responseJson) => {
if (responseJson.success === false) {
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 activated for your organization!")
}
return response.json()
})
.then((responseJson) => {
if (responseJson.success === false) {
toast("Failed to activate the app")
} else {
toast("App activated for your organization! Refresh the page to use the app.")
if (refresh === true) {
getApps()
}
if (refresh === true) {
getApps()
}
})
.catch(error => {
//toast(error.toString())
console.log("Activate app error: ", error.toString())
});
}
})
.catch(error => {
//toast(error.toString())
console.log("Deactivate app error: ", error.toString())
});
}