Merge branch '1.4.0' into tenzir
This commit is contained in:
+595
-101
@@ -201,7 +201,9 @@ const Admin = (props) => {
|
||||
const [showApiKey, setShowApiKey] = useState(false);
|
||||
const [billingInfo, setBillingInfo] = React.useState({});
|
||||
const [selectedStatus, setSelectedStatus] = React.useState([]);
|
||||
|
||||
const [webHooks, setWebHooks] = React.useState([]);
|
||||
const [allSchedules, setAllSchedules] = React.useState([]);
|
||||
const [pipelines, setPipelines] = React.useState([]);
|
||||
const [, forceUpdate] = React.useState();
|
||||
|
||||
const [showDeleteAccountTextbox, setShowDeleteAccountTextbox] =
|
||||
@@ -214,22 +216,32 @@ const Admin = (props) => {
|
||||
getUsers();
|
||||
|
||||
setTimeout(() => {
|
||||
if (adminTab === 3) {
|
||||
window.scroll({
|
||||
top: 450,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
const foundTab = params["admin_tab"]
|
||||
if (foundTab !== null && foundTab !== undefined) {
|
||||
if (adminTab === 3) {
|
||||
window.scroll({
|
||||
top: 450,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 1500);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.scroll({
|
||||
top: 450,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
const foundTab = params["admin_tab"]
|
||||
if (foundTab !== null && foundTab !== undefined) {
|
||||
window.scroll({
|
||||
top: 450,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
}, [adminTab]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -247,7 +259,11 @@ const Admin = (props) => {
|
||||
) {
|
||||
handleGetSubOrgs(userdata.active_org.id);
|
||||
} else console.log("error in user data");
|
||||
}, [userdata]);
|
||||
}, [userdata]);
|
||||
|
||||
useEffect(() => {
|
||||
handleGetAllTriggers()
|
||||
}, []);
|
||||
|
||||
const isCloud =
|
||||
window.location.host === "localhost:3002" ||
|
||||
@@ -731,6 +747,32 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
});
|
||||
};
|
||||
|
||||
const handleGetAllTriggers = () => {
|
||||
fetch(globalUrl + "/api/v1/triggers", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for getting all triggers");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined
|
||||
setAllSchedules(responseJson.schedules || []);
|
||||
// setPipelines(responseJson.pipelines || []);
|
||||
})
|
||||
.catch((error) => {
|
||||
// toast(error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const deleteSchedule = (data) => {
|
||||
// FIXME - add some check here ROFL
|
||||
console.log("INPUT: ", data);
|
||||
@@ -756,11 +798,11 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
if (responseJson["success"] === false) {
|
||||
toast("Failed stopping schedule");
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
getSchedules();
|
||||
}, 1500);
|
||||
//toast("Successfully stopped schedule!")
|
||||
toast("Successfully stopped schedule!");
|
||||
}
|
||||
|
||||
setTimeout(handleGetAllTriggers, 1000);
|
||||
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
@@ -768,6 +810,189 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
});
|
||||
};
|
||||
|
||||
const startSchedule = (trigger) => {
|
||||
if (trigger.name.length <= 0) {
|
||||
toast("Error: name can't be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Creating schedule");
|
||||
const data = {
|
||||
name: trigger.name,
|
||||
frequency: trigger.frequency,
|
||||
execution_argument: trigger.argument,
|
||||
environment: trigger.environment,
|
||||
id: trigger.id,
|
||||
start: trigger.start_node,
|
||||
};
|
||||
|
||||
fetch(`${globalUrl}/api/v1/workflows/${trigger.workflow_id}/schedule`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for stream results :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (!responseJson.success) {
|
||||
toast("Failed to set schedule: " + responseJson.reason);
|
||||
} else {
|
||||
toast("Successfully created schedule");
|
||||
}
|
||||
setTimeout(handleGetAllTriggers, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
//toast(error.toString());
|
||||
console.log("Get schedule error: ", error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const deleteWebhook = (trigger) => {
|
||||
if (trigger === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for stream results :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success) {
|
||||
toast("Successfully stopped webhook");
|
||||
} else {
|
||||
if (responseJson.reason !== undefined) {
|
||||
toast("Failed stopping webhook: " + responseJson.reason);
|
||||
}
|
||||
}
|
||||
setTimeout(handleGetAllTriggers, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast(
|
||||
"Delete webhook error. Contact support or check logs if this persists.",
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const startWebHook = (trigger) => {
|
||||
const hookname = trigger.info.name;
|
||||
if (hookname.length === 0) {
|
||||
toast("Missing name");
|
||||
return;
|
||||
}
|
||||
|
||||
if (trigger.id.length !== 36) {
|
||||
toast("Missing id");
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Starting webhook");
|
||||
|
||||
const data = {
|
||||
name: hookname,
|
||||
type: "webhook",
|
||||
id: trigger.id,
|
||||
workflow: trigger.workflows[0],
|
||||
start: trigger.start,
|
||||
environment: trigger.environment,
|
||||
auth: trigger.auth,
|
||||
custom_response: trigger.custom_response,
|
||||
version: trigger.version,
|
||||
version_timeout: 15,
|
||||
};
|
||||
|
||||
console.log("Trigger data: ", data);
|
||||
|
||||
fetch(globalUrl + "/api/v1/hooks/new", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success) {
|
||||
// Set the status
|
||||
toast("Successfully started webhook");
|
||||
} else {
|
||||
toast("Failed starting webhook: " + responseJson.reason);
|
||||
}
|
||||
setTimeout(handleGetAllTriggers, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
//console.log(error.toString());
|
||||
console.log("New webhook error: ", error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const changePipelineState = (pipeline, state) => {
|
||||
if (state.trim() === "") {
|
||||
toast("state is not defined");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: pipeline.name,
|
||||
type: state,
|
||||
environment: pipeline.environment,
|
||||
workflow_id: pipeline.workflow_id,
|
||||
trigger_id: pipeline.trigger_id,
|
||||
};
|
||||
|
||||
if (state === "start") toast("starting the pipeline");
|
||||
else toast("stopping the pipeline");
|
||||
|
||||
const url = `${globalUrl}/api/v1/triggers/pipeline`;
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for stream results :O!");
|
||||
toast("Failed to update the pipeline state");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (!responseJson.success) {
|
||||
toast("Failed to update the pipeline: " + responseJson.reason);
|
||||
} else {
|
||||
if (state === "start") toast("Successfully created pipeline");
|
||||
else toast("Sucessfully stopped the pipeline");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
//toast(error.toString());
|
||||
console.log("Get schedule error: ", error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
if (
|
||||
userdata.support === true &&
|
||||
selectedOrganization.id !== "" &&
|
||||
@@ -3127,7 +3352,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
color="textSecondary"
|
||||
style={{ marginLeft: 0 }}
|
||||
>
|
||||
On this page organization admins can configure organisations, and
|
||||
On this page organization admins can configure organizations, and
|
||||
sub-orgs (MSSP).{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
@@ -4384,7 +4609,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
/>
|
||||
);
|
||||
|
||||
const schedulesView =
|
||||
const schedulesView =
|
||||
curTab === 5 ? (
|
||||
<div>
|
||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||
@@ -4408,88 +4633,357 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
/>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Interval"
|
||||
style={{ maxWidth: 200, minWidth: 200 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Environment"
|
||||
style={{ maxWidth: 150, minWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Workflow"
|
||||
style={{ maxWidth: 315, minWidth: 315 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Argument"
|
||||
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
|
||||
/>
|
||||
<ListItemText primary="Actions" />
|
||||
<ListItemText primary="Delegation" />
|
||||
</ListItem>
|
||||
{schedules === undefined || schedules === null
|
||||
? null
|
||||
: schedules.map((schedule, index) => {
|
||||
var bgColor = "#27292d";
|
||||
if (index % 2 === 0) {
|
||||
bgColor = "#1f2023";
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 200, minWidth: 200 }}
|
||||
primary={
|
||||
schedule.environment === "cloud" ||
|
||||
schedule.environment === "" ||
|
||||
schedule.frequency.length > 0 ? (
|
||||
schedule.frequency
|
||||
) : (
|
||||
<span>{schedule.seconds} seconds</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 150, minWidth: 150 }}
|
||||
primary={schedule.environment}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 315, minWidth: 315 }}
|
||||
primary={
|
||||
<a
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
href={`/workflows/${schedule.workflow_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{schedule.workflow_id}
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={schedule.argument.replaceAll('\\"', '"')}
|
||||
style={{
|
||||
minWidth: 300,
|
||||
maxWidth: 300,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
<ListItemText>
|
||||
<Button
|
||||
style={{}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => deleteSchedule(schedule)}
|
||||
{allSchedules === undefined ||
|
||||
allSchedules === null ||
|
||||
allSchedules.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
padding: "20px",
|
||||
color: "#666",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
No schedules found.
|
||||
</div>
|
||||
) : (
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Interval"
|
||||
style={{ maxWidth: 200, minWidth: 200 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Environment"
|
||||
style={{ maxWidth: 150, minWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Workflow"
|
||||
style={{ maxWidth: 315, minWidth: 315 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Argument"
|
||||
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
|
||||
/>
|
||||
<ListItemText primary="Actions" />
|
||||
<ListItemText primary="Delegation" />
|
||||
</ListItem>
|
||||
{allSchedules.map((schedule, index) => {
|
||||
var bgColor = "#27292d";
|
||||
if (index % 2 === 0) {
|
||||
bgColor = "#1f2023";
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 200, minWidth: 200 }}
|
||||
primary={
|
||||
schedule.environment === "cloud" ||
|
||||
schedule.environment === "" ||
|
||||
schedule.frequency.length > 0 ? (
|
||||
schedule.frequency
|
||||
) : (
|
||||
<span>{schedule.seconds} seconds</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 150, minWidth: 150 }}
|
||||
primary={schedule.environment}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 315, minWidth: 315 }}
|
||||
primary={
|
||||
<a
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
href={`/workflows/${schedule.workflow_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Stop schedule
|
||||
</Button>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
{schedule.workflow_id}
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={schedule.wrapped_argument.replaceAll('\\"', '"')}
|
||||
style={{
|
||||
minWidth: 300,
|
||||
maxWidth: 300,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
<ListItemText>
|
||||
<Button
|
||||
style={{}}
|
||||
variant={
|
||||
schedule.status === "running" ? "contained" : "outlined"
|
||||
}
|
||||
disabled={schedule.status === "uninitialized"}
|
||||
onClick={() => {
|
||||
if (schedule.status === "running") {
|
||||
deleteSchedule(schedule);
|
||||
} else startSchedule(schedule);
|
||||
}}
|
||||
>
|
||||
{schedule.status === "running"
|
||||
? "Stop Schedule"
|
||||
: "Start Schedule"}
|
||||
</Button>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||
<h2 style={{ display: "inline" }}>WebHooks</h2>
|
||||
</div>
|
||||
|
||||
<Divider
|
||||
style={{
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
/>
|
||||
{webHooks === undefined || webHooks === null || webHooks.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
padding: "20px",
|
||||
color: "#666",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
No webhooks found.
|
||||
</div>
|
||||
) : (
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Name"
|
||||
style={{ maxWidth: 200, minWidth: 200 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Environment"
|
||||
style={{ maxWidth: 150, minWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Workflow"
|
||||
style={{ maxWidth: 315, minWidth: 315 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Url"
|
||||
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
|
||||
/>
|
||||
<ListItemText primary="Actions" />
|
||||
</ListItem>
|
||||
{webHooks.map((webhook, index) => {
|
||||
var bgColor = "#27292d";
|
||||
if (index % 2 === 0) {
|
||||
bgColor = "#1f2023";
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 200, minWidth: 200 }}
|
||||
primary={webhook.info.name}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 150, minWidth: 150 }}
|
||||
primary={webhook.environment}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 315, minWidth: 315 }}
|
||||
primary={
|
||||
<a
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
href={`/workflows/${webhook.workflows[0]}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{webhook.workflows[0]}
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
|
||||
<ListItemText
|
||||
style={{ marginLeft: 10, maxWidth: 100, minWidth: 100 }}
|
||||
primary={
|
||||
webhook.info.url === undefined || webhook.info.url === 0 ? (
|
||||
""
|
||||
) : (
|
||||
<Tooltip
|
||||
title={"Copy URL"}
|
||||
style={{}}
|
||||
aria-label={"Copy URL"}
|
||||
>
|
||||
<IconButton
|
||||
style={{}}
|
||||
onClick={() => {
|
||||
const elementName = "copy_element_shuffle";
|
||||
var copyText = document.getElementById(elementName);
|
||||
if (copyText !== null && copyText !== undefined) {
|
||||
const clipboard = navigator.clipboard;
|
||||
if (clipboard === undefined) {
|
||||
toast("Can only copy over HTTPS (port 3443)");
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(webhook.info.url);
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(
|
||||
0,
|
||||
99999,
|
||||
); /* For mobile devices */
|
||||
|
||||
/* Copy the text inside the text field */
|
||||
document.execCommand("copy");
|
||||
|
||||
toast("URL copied to clipboard");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FileCopyIcon
|
||||
style={{ color: "rgba(255,255,255,0.8)" }}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<ListItemText>
|
||||
<Button
|
||||
style={{ marginLeft: "18%" }}
|
||||
variant={
|
||||
webhook.status === "running" ? "contained" : "outlined"
|
||||
}
|
||||
disabled={webhook.status === "uninitialized"}
|
||||
onClick={() => {
|
||||
if (webhook.status === "running") {
|
||||
deleteWebhook(webhook);
|
||||
} else startWebHook(webhook);
|
||||
}}
|
||||
>
|
||||
{webhook.status === "running"
|
||||
? "Stop webhook"
|
||||
: "Start Webhook"}
|
||||
</Button>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
)}
|
||||
|
||||
{/* <div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||
<h2 style={{ display: "inline" }}>Tenzir Pipelines</h2>
|
||||
<span style={{ marginLeft: 25 }}>
|
||||
Controls a pipeline to run things.{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="/docs/triggers#pipelines"
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Divider
|
||||
style={{
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
/>
|
||||
{pipelines === undefined ||
|
||||
pipelines === null ||
|
||||
pipelines.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
padding: "20px",
|
||||
color: "#666",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
No pipelines found.
|
||||
</div>
|
||||
) : (
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Name"
|
||||
style={{ maxWidth: 200, minWidth: 200 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Environment"
|
||||
style={{ maxWidth: 150, minWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Workflow"
|
||||
style={{ maxWidth: 315, minWidth: 315 }}
|
||||
/>
|
||||
<ListItemText primary="Actions" />
|
||||
</ListItem>
|
||||
{pipelines.map((pipeline, index) => {
|
||||
var bgColor = "#27292d";
|
||||
if (index % 2 === 0) {
|
||||
bgColor = "#1f2023";
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 200, minWidth: 200 }}
|
||||
primary={pipeline.name}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 150, minWidth: 150 }}
|
||||
primary={pipeline.environment}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{ maxWidth: 315, minWidth: 315 }}
|
||||
primary={
|
||||
<a
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
href={`/workflows/${pipeline.workflow_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{pipeline.workflow_id}
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
<ListItemText>
|
||||
<Button
|
||||
style={{ marginLeft: "18%" }}
|
||||
variant={
|
||||
pipeline.status === "running" ? "contained" : "outlined"
|
||||
}
|
||||
disabled={pipeline.status === "uninitialized"}
|
||||
onClick={() => {
|
||||
if (pipeline.status === "running") {
|
||||
changePipelineState(pipeline, "stop");
|
||||
} else changePipelineState(pipeline, "start");
|
||||
}}
|
||||
>
|
||||
{pipeline.status === "running"
|
||||
? "Stop pipeline"
|
||||
: "Start pipeline"}
|
||||
</Button>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
)}*/}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
|
||||
@@ -120,9 +120,12 @@ import {
|
||||
Add as AddIcon,
|
||||
} from "@mui/icons-material";
|
||||
|
||||
import * as cytoscape from "cytoscape";
|
||||
import * as edgehandles from "cytoscape-edgehandles";
|
||||
//import * as cytoscape from "cytoscape";
|
||||
import cytoscape from "cytoscape";
|
||||
|
||||
import edgehandles from "cytoscape-edgehandles";
|
||||
import CytoscapeComponent from "react-cytoscapejs";
|
||||
|
||||
import Draggable from "react-draggable";
|
||||
import cytoscapestyle from "../defaultCytoscapeStyle.jsx";
|
||||
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
|
||||
@@ -137,6 +140,8 @@ import ExtraApps from "../components/ExtraApps.jsx"
|
||||
import EditWorkflow from "../components/EditWorkflow.jsx"
|
||||
// import AppStats from "../components/AppStats.jsx";
|
||||
|
||||
cytoscape.use(edgehandles);
|
||||
|
||||
export const triggers = [
|
||||
{
|
||||
name: "Webhook",
|
||||
@@ -236,15 +241,7 @@ export const triggers = [
|
||||
},
|
||||
];
|
||||
|
||||
// http://apps.cytoscape.org/apps/yfileslayoutalgorithms
|
||||
cytoscape.use(edgehandles);
|
||||
//cytoscape.use(clipboard);
|
||||
//cytoscape.use(undoRedo);
|
||||
//cytoscape.use(cxtmenu);
|
||||
|
||||
// Adds specific text to items
|
||||
//import popper from 'cytoscape-popper';
|
||||
//cytoscape.use(popper);
|
||||
|
||||
// https://stackoverflow.com/questions/19014250/rerender-view-on-browser-resize-with-react
|
||||
function useWindowSize() {
|
||||
@@ -592,7 +589,27 @@ const AngularWorkflow = (defaultprops) => {
|
||||
]
|
||||
}*/
|
||||
]
|
||||
}]
|
||||
},{
|
||||
"name": "Communication",
|
||||
"description": "Available actions for communication",
|
||||
"label": "Communication",
|
||||
"parameters": [{
|
||||
"name": "action",
|
||||
"value": "list_messages",
|
||||
"options": [
|
||||
"list_messages",
|
||||
"send_message",
|
||||
],
|
||||
"required": true,
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"value": "",
|
||||
"required": false,
|
||||
"multiline": true,
|
||||
}]
|
||||
},
|
||||
]
|
||||
}]
|
||||
|
||||
/*
|
||||
@@ -685,7 +702,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
props.userdata.active_org !== undefined
|
||||
? props.userdata.active_org.cloud_sync === true
|
||||
: false;
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "migration.shuffler.io";
|
||||
|
||||
const appBarSize = isCloud ? 75 : 72;
|
||||
const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"];
|
||||
@@ -3996,42 +4013,50 @@ const AngularWorkflow = (defaultprops) => {
|
||||
|
||||
workflow.actions.push(newNodeData);
|
||||
|
||||
const sourcebranches = workflow.branches.filter(
|
||||
(foundbranch) => foundbranch.source_id === parentNode.data("id")
|
||||
);
|
||||
const destinationbranches = workflow.branches.filter(
|
||||
(foundbranch) =>
|
||||
foundbranch.destination_id === parentNode.data("id")
|
||||
);
|
||||
const sourcebranches = workflow.branches.filter((foundbranch) => foundbranch.source_id === parentNode.data("id"))
|
||||
|
||||
|
||||
const destinationbranches = workflow.branches.filter((foundbranch) => foundbranch.destination_id === parentNode.data("id"))
|
||||
|
||||
|
||||
for (var sourceBranchesKey in sourcebranches) {
|
||||
var newbranch = JSON.parse(JSON.stringify(sourcebranches[sourceBranchesKey]));
|
||||
newbranch.id = uuidv4();
|
||||
newbranch.source_id = newNodeData.id;
|
||||
|
||||
newbranch._id = newbranch.id;
|
||||
newbranch.source = newbranch.source_id;
|
||||
newbranch.target = newbranch.destination_id;
|
||||
cy.add({
|
||||
group: "edges",
|
||||
data: newbranch,
|
||||
});
|
||||
newbranch.id = uuidv4()
|
||||
newbranch.source_id = newNodeData.id
|
||||
|
||||
newbranch._id = newbranch.id
|
||||
newbranch.source = newbranch.source_id
|
||||
newbranch.target = newbranch.destination_id
|
||||
cy.add({
|
||||
group: "edges",
|
||||
data: newbranch,
|
||||
})
|
||||
}
|
||||
|
||||
for (var destinationBranchesKey in destinationbranches) {
|
||||
var newbranch = JSON.parse(
|
||||
JSON.stringify(destinationbranches[destinationBranchesKey])
|
||||
);
|
||||
newbranch.id = uuidv4();
|
||||
newbranch.destination_id = newNodeData.id;
|
||||
var newbranch = JSON.parse(JSON.stringify(destinationbranches[destinationBranchesKey]))
|
||||
|
||||
newbranch._id = newbranch.id;
|
||||
newbranch.source = newbranch.source_id;
|
||||
newbranch.target = newbranch.destination_id;
|
||||
cy.add({
|
||||
group: "edges",
|
||||
data: newbranch,
|
||||
});
|
||||
const sourcenode = cy.getElementById(newbranch.source_id)
|
||||
if (sourcenode !== null && sourcenode !== undefined) {
|
||||
const sourcedata = sourcenode.data()
|
||||
|
||||
if (sourcedata.trigger_type !== "SUBFLOW" && sourcedata.trigger_type !== "USERINPUT") {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
newbranch.id = uuidv4()
|
||||
newbranch.destination_id = newNodeData.id
|
||||
|
||||
newbranch._id = newbranch.id
|
||||
newbranch.source = newbranch.source_id
|
||||
newbranch.target = newbranch.destination_id
|
||||
cy.add({
|
||||
group: "edges",
|
||||
data: newbranch,
|
||||
})
|
||||
}
|
||||
|
||||
//event.target.unselect();
|
||||
@@ -4783,7 +4808,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
event.target.remove()
|
||||
|
||||
//console.log("Found branch already!")
|
||||
toast("Triggers can have exactly one target node")
|
||||
toast.error("Triggers can have exactly one target node")
|
||||
return
|
||||
|
||||
|
||||
@@ -5194,7 +5219,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
data: newdata,
|
||||
})
|
||||
|
||||
toast("You must STOP the trigger before deleting its branches")
|
||||
toast.error("You must STOP the trigger before deleting its branches")
|
||||
} catch (e) {
|
||||
console.log("Failed re-adding edge: ", e)
|
||||
}
|
||||
@@ -13130,7 +13155,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
if (trigger.id === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
@@ -13143,32 +13168,34 @@ const AngularWorkflow = (defaultprops) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for stream results :O!");
|
||||
}
|
||||
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (!responseJson.success) {
|
||||
if (responseJson.reason !== undefined) {
|
||||
toast("Failed to stop webhook: " + responseJson.reason);
|
||||
}
|
||||
} else {
|
||||
toast("Successfully stopped webhook");
|
||||
}
|
||||
if (workflow.triggers[triggerindex] !== undefined) {
|
||||
workflow.triggers[triggerindex].status = "stopped";
|
||||
}
|
||||
|
||||
if (responseJson.success) {
|
||||
// Set the status
|
||||
saveWorkflow(workflow);
|
||||
} else {
|
||||
if (responseJson.reason !== undefined) {
|
||||
toast("Failed stopping webhook: " + responseJson.reason);
|
||||
}
|
||||
}
|
||||
|
||||
trigger.status = "stopped";
|
||||
setWorkflow(workflow);
|
||||
setSelectedTrigger(trigger);
|
||||
setWorkflow(workflow);
|
||||
saveWorkflow(workflow);
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
//toast(error.toString());
|
||||
toast("Delete webhook error. Contact support or check logs if this persists.")
|
||||
toast(
|
||||
"Delete webhook error. Contact support or check logs if this persists.",
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// POST to /api/v1/workflows
|
||||
const createWorkflow = (workflow, trigger_index) => {
|
||||
@@ -17221,6 +17248,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
return "Run this workflow in a local environment to increase the timeout. Go to https://shuffler.io/admin?tab=environments to create an environment to connect to"
|
||||
}
|
||||
|
||||
if (stringjson.toLowerCase().includes("invalid header")) {
|
||||
return "A header or authentication token in the app is invalid. Check the app's configuration"
|
||||
}
|
||||
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1037,55 +1037,41 @@ const AppCreator = (defaultprops) => {
|
||||
"schema"
|
||||
] !== null
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
methodvalue["requestBody"]["content"]["application/xml"][
|
||||
"schema"
|
||||
]["properties"] !== undefined
|
||||
) {
|
||||
var tmpobject = {};
|
||||
for (let [prop, propvalue] of Object.entries(methodvalue["requestBody"]["content"]["application/xml"]["schema"]["properties"])) {
|
||||
|
||||
tmpobject[prop] = `\$\{${prop}\}`;
|
||||
}
|
||||
try {
|
||||
if (
|
||||
methodvalue["requestBody"]["content"]["application/xml"][
|
||||
"schema"
|
||||
]["properties"] !== undefined
|
||||
) {
|
||||
var tmpobject = {};
|
||||
for (let [prop, propvalue] of Object.entries(methodvalue["requestBody"]["content"]["application/xml"]["schema"]["properties"])) {
|
||||
|
||||
tmpobject[prop] = `\$\{${prop}\}`;
|
||||
}
|
||||
|
||||
for (let [subkey,subkeyval] in Object.entries(methodvalue["requestBody"]["content"]["application/xml"]["schema"]["required"])) {
|
||||
const tmpitem =
|
||||
methodvalue["requestBody"]["content"][
|
||||
"application/xml"
|
||||
]["schema"]["required"][subkey];
|
||||
tmpobject[tmpitem] = `\$\{${tmpitem}\}`;
|
||||
}
|
||||
for (let [subkey,subkeyval] in Object.entries(methodvalue["requestBody"]["content"]["application/xml"]["schema"]["required"])) {
|
||||
const tmpitem =
|
||||
methodvalue["requestBody"]["content"][
|
||||
"application/xml"
|
||||
]["schema"]["required"][subkey];
|
||||
tmpobject[tmpitem] = `\$\{${tmpitem}\}`;
|
||||
}
|
||||
|
||||
//console.log("OBJ XML: ", tmpobject)
|
||||
//newaction["body"] = XML.stringify(tmpobject, null, 2)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("RequestBody xml error: ", e, path)
|
||||
}
|
||||
//console.log("OBJ XML: ", tmpobject)
|
||||
//newaction["body"] = XML.stringify(tmpobject, null, 2)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("RequestBody xml error: ", e, path)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
methodvalue["requestBody"]["content"]["example"] !== undefined
|
||||
) {
|
||||
if (
|
||||
methodvalue["requestBody"]["content"]["example"][
|
||||
"example"
|
||||
] !== undefined
|
||||
) {
|
||||
newaction["body"] =
|
||||
methodvalue["requestBody"]["content"]["example"][
|
||||
"example"
|
||||
];
|
||||
//JSON.stringify(tmpobject, null, 2)
|
||||
if (methodvalue["requestBody"]["content"]["example"] !== undefined) {
|
||||
if (methodvalue["requestBody"]["content"]["example"]["example"] !== undefined) {
|
||||
newaction["body"] = methodvalue["requestBody"]["content"]["example"]["example"]
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
methodvalue["requestBody"]["content"][
|
||||
"multipart/form-data"
|
||||
] !== undefined
|
||||
) {
|
||||
|
||||
if (methodvalue["requestBody"]["content"]["multipart/form-data"] !== undefined) {
|
||||
if (
|
||||
methodvalue["requestBody"]["content"][
|
||||
"multipart/form-data"
|
||||
@@ -1553,8 +1539,10 @@ const AppCreator = (defaultprops) => {
|
||||
} else if (parameter.in === "body") {
|
||||
// FIXME: Add tracking for components
|
||||
// E.G: https://raw.githubusercontent.com/owentl/Shuffle/master/gosecure.yaml
|
||||
if (parameter.example !== undefined) {
|
||||
newaction.body = parameter.example;
|
||||
if (parameter.example !== undefined && parameter.example !== null) {
|
||||
if (newaction.body === undefined || newaction.body === null || newaction.body.length < 5) {
|
||||
newaction.body = parameter.example
|
||||
}
|
||||
}
|
||||
} else if (parameter.in === "header") {
|
||||
newaction.headers += `${parameter.name}=${parameter.example}\n`;
|
||||
@@ -1566,6 +1554,13 @@ const AppCreator = (defaultprops) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if body is valid JSON.
|
||||
if (newaction.body !== undefined && newaction.body !== null && newaction.body.length > 0) {
|
||||
// Trim starting / ending newlines, spaces and tabs
|
||||
newaction.body = newaction.body.trim()
|
||||
}
|
||||
|
||||
|
||||
if (newaction.name === "" || newaction.name === undefined) {
|
||||
// Find a unique part of the string
|
||||
// FIXME: Looks for length between /, find the one where they differ
|
||||
@@ -1920,11 +1915,11 @@ const AppCreator = (defaultprops) => {
|
||||
|
||||
setProjectCategories(all_categories);
|
||||
|
||||
// Rearrange them by which has action_label
|
||||
const firstActions = newActions.filter(data => data.action_label !== undefined && data.action_label !== null && data.action_label !== "No Label")
|
||||
console.log("First actions: ", firstActions)
|
||||
const secondActions = newActions.filter(data => data.action_label === undefined || data.action_label === null || data.action_label === "No Label")
|
||||
newActions = firstActions.concat(secondActions)
|
||||
// Rearrange them by which has action_label
|
||||
const firstActions = newActions.filter(data => data.action_label !== undefined && data.action_label !== null && data.action_label !== "No Label")
|
||||
console.log("First actions: ", firstActions)
|
||||
const secondActions = newActions.filter(data => data.action_label === undefined || data.action_label === null || data.action_label === "No Label")
|
||||
newActions = firstActions.concat(secondActions)
|
||||
setActions(newActions);
|
||||
//data.paths[item.url][item.method.toLowerCase()]["x-label"] = item.action_label
|
||||
|
||||
@@ -3078,7 +3073,10 @@ const AppCreator = (defaultprops) => {
|
||||
Refresh-token URL for Oauth2 (Optional)
|
||||
</Typography>
|
||||
<TextField
|
||||
style={{ margin: 0, flex: "1", backgroundColor: inputColor }}
|
||||
style={{
|
||||
margin: 0, flex: "1", backgroundColor: inputColor,
|
||||
border: !refreshUrl.startsWith("http") || refreshUrl.includes("//shuffler.") ? "2px solid red" : "inherit",
|
||||
}}
|
||||
fullWidth={true}
|
||||
placeholder="The URL to retrieve refresh-tokens at"
|
||||
type="name"
|
||||
@@ -3086,8 +3084,9 @@ const AppCreator = (defaultprops) => {
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
value={refreshUrl}
|
||||
helperText={!refreshUrl.startsWith("http") || refreshUrl.includes("//shuffler.")? "Must start with http(s):// and can not contain shuffler.io" : ""}
|
||||
onChange={(e) => setRefreshUrl(e.target.value)}
|
||||
onBlur={(event) => {
|
||||
onBlur={(event) => {
|
||||
var tmpstring = event.target.value.trim();
|
||||
|
||||
if (
|
||||
@@ -3153,7 +3152,7 @@ const AppCreator = (defaultprops) => {
|
||||
</Typography>
|
||||
<div style={{display: "flex", marginTop: 10, }}>
|
||||
<div style={{flex: 4,}}>
|
||||
Key
|
||||
Key
|
||||
<TextField
|
||||
required
|
||||
style={{ marginTop: 0, backgroundColor: inputColor }}
|
||||
@@ -3166,12 +3165,17 @@ const AppCreator = (defaultprops) => {
|
||||
value={parameterName}
|
||||
helperText={
|
||||
<span style={{ color: "white", marginBottom: "2px" }}>
|
||||
Can't be empty or contain any of the following: !#$%&'^"+-._~|]+$
|
||||
Can't be empty or contain any of the following: !#$%&'^"+-._~|]+$:=
|
||||
</span>
|
||||
}
|
||||
onChange={(e) => {
|
||||
setParameterName(e.target.value);
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
var tmpstring = event.target.value.trim()
|
||||
|
||||
// Check if tmpstring has any of the illegal characters in it
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
|
||||
@@ -207,7 +207,7 @@ const EditWorkflow = (props) => {
|
||||
|
||||
const cyDummy = cytoscape();
|
||||
if (!cyDummy.edgehandles) {
|
||||
cytoscape.use(edgehandles);
|
||||
//cytoscape.use(edgehandles);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+285
-175
@@ -1,206 +1,316 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
|
||||
import theme from '../theme.jsx';
|
||||
import theme from "../theme.jsx";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import AppGrid from "../components/AppGrid.jsx"
|
||||
import WorkflowGrid from "../components/WorkflowGrid.jsx"
|
||||
import CreatorGrid from "../components/CreatorGrid.jsx"
|
||||
import DocsGrid from "../components/DocsGrid.jsx"
|
||||
import DiscordChat from "../components/DiscordChat.jsx";
|
||||
import AppGrid from "../components/AppGrid.jsx";
|
||||
import WorkflowGrid from "../components/WorkflowGrid.jsx";
|
||||
import CreatorGrid from "../components/CreatorGrid.jsx";
|
||||
import DocsGrid from "../components/DocsGrid.jsx";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Tabs, Tab, setRef } from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import DiscordChat from "../components/DiscordChat.jsx";
|
||||
|
||||
import {
|
||||
Tabs,
|
||||
Tab,
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
Apps as AppsIcon,
|
||||
Code as CodeIcon,
|
||||
Chat as ChatIcon,
|
||||
EmojiObjects as EmojiObjectsIcon,
|
||||
Description as DescriptionIcon,
|
||||
Apps as AppsIcon,
|
||||
Code as CodeIcon,
|
||||
EmojiObjects as EmojiObjectsIcon,
|
||||
Chat as ChatIcon,
|
||||
BorderBottom,
|
||||
} from "@mui/icons-material";
|
||||
|
||||
import PeopleAltOutlinedIcon from '@mui/icons-material/PeopleAltOutlined';
|
||||
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
|
||||
|
||||
// Should be different if logged in :|
|
||||
const Search = (props) => {
|
||||
const { globalUrl, isLoaded, serverside, userdata, hidemargins, isHeader } = props;
|
||||
let navigate = useNavigate();
|
||||
const { globalUrl, isLoaded, serverside, userdata, hidemargins, isHeader } =
|
||||
props;
|
||||
let navigate = useNavigate();
|
||||
|
||||
const [curTab, setCurTab] = useState(0);
|
||||
const iconStyle = { marginRight: isHeader ? null : 10 };
|
||||
const [curTab, setCurTab] = useState(0);
|
||||
const iconStyle = { marginRight: isHeader ? null : 10 };
|
||||
|
||||
useEffect(() => {
|
||||
if (serverside !== true && window.location.search !== undefined && window.location.search !== null) {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||
const params = Object.fromEntries(urlSearchParams.entries())
|
||||
const foundTab = params["tab"]
|
||||
if (foundTab !== null && foundTab !== undefined) {
|
||||
for (var key in Object.keys(views)) {
|
||||
const value = views[key]
|
||||
console.log(key, value)
|
||||
if (value === foundTab) {
|
||||
setConfig("", key)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (
|
||||
serverside !== true &&
|
||||
window.location.search !== undefined &&
|
||||
window.location.search !== null
|
||||
) {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
const foundTab = params["tab"];
|
||||
if (foundTab !== null && foundTab !== undefined) {
|
||||
for (var key in Object.keys(views)) {
|
||||
const value = views[key];
|
||||
console.log(key, value);
|
||||
if (value === foundTab) {
|
||||
setConfig("", key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (serverside === true) {
|
||||
return null
|
||||
}
|
||||
//Stop unnecessariry re-rendering of the component to improve performace
|
||||
const MemoizedAppGrid = useMemo(() => <AppGrid
|
||||
maxRows={4}
|
||||
isHeader={true}
|
||||
showSuggestion={true}
|
||||
globalUrl={globalUrl}
|
||||
isMobile={isMobile}
|
||||
userdata={userdata}
|
||||
/>, [curTab]);
|
||||
|
||||
const bodyDivStyle = {
|
||||
margin: "auto",
|
||||
maxWidth: 1024,
|
||||
scrollX: "hidden",
|
||||
overflowX: "hidden",
|
||||
justifyContent: isHeader ? "center" : null,
|
||||
}
|
||||
const MemoizedWorkflowGrid = useMemo(() => <WorkflowGrid
|
||||
maxRows={3}
|
||||
showSuggestion={true}
|
||||
globalUrl={globalUrl}
|
||||
isMobile={isMobile}
|
||||
userdata={userdata}
|
||||
/>, [curTab]);
|
||||
|
||||
const boxStyle = {
|
||||
color: "white",
|
||||
flex: "1",
|
||||
marginLeft: isHeader ? null : 10,
|
||||
marginRight: isHeader ? null : 10,
|
||||
paddingLeft: isHeader ? null : 30,
|
||||
paddingRight: isHeader ? null : 30,
|
||||
paddingBottom: isHeader ? null : 30,
|
||||
paddingTop: hidemargins === true ? 0 : isHeader ? null : 30,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflowX: "hidden",
|
||||
minHeight: 400,
|
||||
}
|
||||
const MemoizedDocsGrid = useMemo(() => <DocsGrid
|
||||
maxRows={6}
|
||||
parsedXs={12}
|
||||
showSuggestion={true}
|
||||
globalUrl={globalUrl}
|
||||
isMobile={isMobile}
|
||||
userdata={userdata}
|
||||
/>, [curTab]);
|
||||
|
||||
const views = {
|
||||
0: "apps",
|
||||
1: "workflows",
|
||||
2: "docs",
|
||||
3: "creators",
|
||||
4: "discord",
|
||||
}
|
||||
const MemoizedCreatorGrid = useMemo(() => <CreatorGrid
|
||||
parsedXs={4}
|
||||
isHeader={true}
|
||||
showSuggestion={true}
|
||||
globalUrl={globalUrl}
|
||||
isMobile={isMobile}
|
||||
userdata={userdata}
|
||||
/>, [curTab]);
|
||||
|
||||
const setConfig = (event, inputValue) => {
|
||||
const newValue = parseInt(inputValue)
|
||||
const MemoizedDiscordChat = useMemo(() => <DiscordChat isMobile={isMobile} />)
|
||||
|
||||
setCurTab(newValue)
|
||||
if (newValue === 0) {
|
||||
document.title = "Shuffle - search - apps";
|
||||
} else if (newValue === 1) {
|
||||
document.title = "Shuffle - search - workflows";
|
||||
} else if (newValue === 2) {
|
||||
document.title = "Shuffle - search - documentation";
|
||||
} else if (newValue === 3) {
|
||||
document.title = "Shuffle - search - creators";
|
||||
} else if (newValue === 4) {
|
||||
document.title = "Shuffle - search - Discord Chat";
|
||||
}else {
|
||||
document.title = "Shuffle - search";
|
||||
}
|
||||
const useStyles = makeStyles({
|
||||
hideIndicator: {
|
||||
display: 'none',
|
||||
},
|
||||
customTab: {
|
||||
justifyContent: 'center',
|
||||
gap: '46px',
|
||||
}
|
||||
});
|
||||
const classes = useStyles();
|
||||
|
||||
if (serverside === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||
const params = Object.fromEntries(urlSearchParams.entries())
|
||||
const foundQuery = params["q"]
|
||||
var extraQ = ""
|
||||
if (foundQuery !== null && foundQuery !== undefined) {
|
||||
extraQ = "&q=" + foundQuery
|
||||
}
|
||||
const bodyDivStyle = {
|
||||
margin: "auto",
|
||||
maxWidth: "100%",
|
||||
scrollX: "hidden",
|
||||
overflowX: "hidden",
|
||||
justifyContent: isHeader ? "center" : null,
|
||||
};
|
||||
|
||||
const boxStyle = {
|
||||
color: "white",
|
||||
flex: "1",
|
||||
marginLeft: isHeader ? null : 10,
|
||||
marginRight: isHeader ? null : 10,
|
||||
paddingLeft: isHeader ? null : 30,
|
||||
paddingRight: isHeader ? null : 30,
|
||||
paddingBottom: isHeader ? null : 30,
|
||||
paddingTop: hidemargins === true ? 0 : isHeader ? null : 30,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflowX: "hidden",
|
||||
width: "100%",
|
||||
minHeight: 400,
|
||||
};
|
||||
|
||||
if ((serverside === false || serverside === undefined) && window.location.pathname.includes("/search")) {
|
||||
navigate(`/search?tab=${views[newValue]}` + extraQ)
|
||||
}
|
||||
}
|
||||
const views = {
|
||||
0: "apps",
|
||||
1: "workflows",
|
||||
2: "docs",
|
||||
3: "creators",
|
||||
};
|
||||
const setConfig = (event, inputValue) => {
|
||||
const newValue = parseInt(inputValue);
|
||||
|
||||
if (isLoaded === false) {
|
||||
return null
|
||||
}
|
||||
setCurTab(newValue);
|
||||
if (newValue === 0) {
|
||||
document.title = "Shuffle - search - apps";
|
||||
} else if (newValue === 1) {
|
||||
document.title = "Shuffle - search - workflows";
|
||||
} else if (newValue === 2) {
|
||||
document.title = "Shuffle - search - documentation";
|
||||
} else if (newValue === 3) {
|
||||
document.title = "Shuffle - search - creators";
|
||||
} else {
|
||||
document.title = "Shuffle - search";
|
||||
}
|
||||
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
const foundQuery = params["q"];
|
||||
var extraQ = "";
|
||||
if (foundQuery !== null && foundQuery !== undefined) {
|
||||
extraQ = "&q=" + foundQuery;
|
||||
}
|
||||
|
||||
// Random names for type & autoComplete. Didn't research :^)
|
||||
const landingpageDataBrowser =
|
||||
<div style={{ paddingBottom: hidemargins === true ? 0 : 100, color: "white", }}>
|
||||
<div style={boxStyle}>
|
||||
<Tabs
|
||||
style={{ width: isHeader ? 765 : 610, margin: isHeader ? null : "auto", marginTop: hidemargins === true ? 0 : isHeader ? null : 25, }}
|
||||
value={curTab}
|
||||
indicatorColor="primary"
|
||||
textColor="secondary"
|
||||
onChange={setConfig}
|
||||
aria-label="disabled tabs example"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
>
|
||||
<Tab
|
||||
label=<span>
|
||||
<AppsIcon style={iconStyle} /> Apps
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
label=<span>
|
||||
<CodeIcon style={iconStyle} /> Workflows
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
label=<span>
|
||||
<DescriptionIcon style={iconStyle} /> Docs
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
label=<span>
|
||||
<EmojiObjectsIcon style={iconStyle} /> Creators
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
label=<span>
|
||||
<ChatIcon style={iconStyle} /> Discord Chat
|
||||
</span>
|
||||
/>
|
||||
if (
|
||||
(serverside === false || serverside === undefined) &&
|
||||
window.location.pathname.includes("/search")
|
||||
) {
|
||||
navigate(`/search?tab=${views[newValue]}` + extraQ);
|
||||
}
|
||||
};
|
||||
|
||||
</Tabs>
|
||||
{curTab === 0 ?
|
||||
<AppGrid maxRows={3} isHeader={true} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
||||
:
|
||||
curTab === 1 ?
|
||||
window.location.pathname === "/search" ?
|
||||
<WorkflowGrid maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
||||
:
|
||||
<WorkflowGrid maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
||||
:
|
||||
curTab === 2 ?
|
||||
<DocsGrid maxRows={6} parsedXs={12} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
||||
:
|
||||
curTab === 3 ?
|
||||
<CreatorGrid parsedXs={4} isHeader={true} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
||||
:
|
||||
curTab === 4 ?
|
||||
<DiscordChat isMobile={isMobile} />
|
||||
:
|
||||
if (isLoaded === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
null}
|
||||
</div>
|
||||
</div>
|
||||
//{/*alternativeView={true} />*/}
|
||||
const StyledTab = styled(Tab)(({ theme }) => ({
|
||||
width: 151,
|
||||
height: 51,
|
||||
padding: "10px 20px",
|
||||
borderRadius: 8,
|
||||
fontWeight: 600,
|
||||
textTransform: "none",
|
||||
border: 'none',
|
||||
"&.Mui-selected": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.common.white,
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: theme.palette.common.white,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const loadedCheck = isLoaded ?
|
||||
<div>
|
||||
<div style={bodyDivStyle}>{landingpageDataBrowser}</div>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
const tabSpanStyling = {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}
|
||||
|
||||
// #1f2023?
|
||||
return (
|
||||
<div style={{}}>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const tabTextStyling = {
|
||||
marginLeft: '5px',
|
||||
color: 'white'
|
||||
}
|
||||
|
||||
|
||||
// Random names for type & autoComplete. Didn't research :^)
|
||||
const landingpageDataBrowser = (
|
||||
<div
|
||||
style={{
|
||||
paddingBottom: hidemargins === true ? 0 : 100,
|
||||
color: "white",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<div style={boxStyle}>
|
||||
<Tabs
|
||||
style={{
|
||||
width: 741,
|
||||
margin: isHeader ? null : "auto",
|
||||
marginTop: hidemargins === true ? 0 : isHeader ? null : 25,
|
||||
backgroundColor: "rgba(33, 33, 33, 1)",
|
||||
borderRadius:8
|
||||
}}
|
||||
value={curTab}
|
||||
indicatorColor="primary"
|
||||
textColor="secondary"
|
||||
onChange={setConfig}
|
||||
aria-label="disabled tabs example"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
classes={{indicator: classes.hideIndicator, root: classes.customTab}}
|
||||
>
|
||||
<StyledTab
|
||||
style={{
|
||||
backgroundColor: curTab === 0 ? theme.palette.primary.main : 'inherit',
|
||||
color: curTab === 0? theme.palette.common.white : 'inherit',
|
||||
}}
|
||||
label={
|
||||
<span style={tabSpanStyling}>
|
||||
<AppsIcon style={iconStyle} />
|
||||
<Typography variant="body1" style={tabTextStyling}>App</Typography>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<StyledTab
|
||||
style={{
|
||||
backgroundColor: curTab ===1 ? theme.palette.primary.main : 'inherit',
|
||||
color: curTab === 1? theme.palette.common.white : 'inherit',
|
||||
}}
|
||||
label={
|
||||
<span style={tabSpanStyling}>
|
||||
<CodeIcon style={iconStyle} />
|
||||
<Typography variant="body1" style={tabTextStyling}>Workflow</Typography>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<StyledTab
|
||||
style={{
|
||||
backgroundColor: curTab === 2 ? theme.palette.primary.main : 'inherit',
|
||||
color: curTab === 2? theme.palette.common.white : 'inherit',
|
||||
}}
|
||||
label={
|
||||
<span style={tabSpanStyling}>
|
||||
<DescriptionOutlinedIcon style={iconStyle} />
|
||||
<Typography variant="body1" style={tabTextStyling}>Docs</Typography>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<StyledTab
|
||||
style={{
|
||||
backgroundColor: curTab === 3 ? theme.palette.primary.main : 'inherit',
|
||||
color: curTab === 3 ? theme.palette.common.white : 'inherit'
|
||||
}}
|
||||
label={
|
||||
<span style={tabSpanStyling}>
|
||||
<PeopleAltOutlinedIcon style={iconStyle} />
|
||||
<Typography variant="body1" style={tabTextStyling}>Creators</Typography>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<StyledTab
|
||||
style={{
|
||||
backgroundColor: curTab === 4 ? theme.palette.primary.main : 'inherit',
|
||||
color: curTab === 4 ? theme.palette.common.white : 'inherit'
|
||||
}}
|
||||
label={
|
||||
<span style={tabSpanStyling}>
|
||||
<ChatIcon style={iconStyle} />
|
||||
<Typography variant="body1" style={{tabTextStyling, whiteSpace: "nowrap", color: "white"}}>Discord Chat</Typography>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Tabs>
|
||||
{curTab === 0 && MemoizedAppGrid}
|
||||
{curTab === 1 && MemoizedWorkflowGrid}
|
||||
{curTab === 2 && MemoizedDocsGrid}
|
||||
{curTab === 3 && MemoizedCreatorGrid}
|
||||
{curTab === 4 && MemoizedDiscordChat}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
//{/*alternativeView={true} />*/}
|
||||
|
||||
const loadedCheck = isLoaded ? (
|
||||
<div>
|
||||
<div style={bodyDivStyle}>{landingpageDataBrowser}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
);
|
||||
|
||||
// #1f2023?
|
||||
return <div style={{}}>{loadedCheck}</div>;
|
||||
};
|
||||
|
||||
export default Search;
|
||||
|
||||
Reference in New Issue
Block a user