Merge branch '2.0.0'

This commit is contained in:
Aditya
2024-12-05 05:20:50 +05:30
136 changed files with 45316 additions and 19511 deletions
+138
View File
@@ -0,0 +1,138 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Typography } from '@mui/material';
import theme from "../theme.jsx";
const NotFound = () => {
const navigate = useNavigate();
const buttonStyle = {
borderRadius: 25,
height: 50,
fontSize: 18,
width: "100%",
marginBottom: "10px",
};
const primaryButtonStyle = {
...buttonStyle,
background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)",
color: "white",
'&:hover': {
background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)",
opacity: 0.9,
}
};
const secondaryButtonStyle = {
...buttonStyle,
background: "#383B40",
border: "1px solid #494949",
color: "white",
'&:hover': {
background: "#434649",
}
};
return (
<div style={{paddingTop: 100, }} className="min-h-screen bg-[#1A1A1A] flex items-center justify-center">
<div
style={{
width: "100%",
maxWidth: "532px",
background: "#212121",
borderRadius: "8px",
padding: "40px",
display: "flex",
flexDirection: "column",
alignItems: "center",
textAlign: "center",
justifySelf: "center",
}}
>
<img
src="/images/logos/orange_logo.svg"
alt="Shuffle Logo"
style={{
height: 44,
width: 44,
marginBottom: 10,
}}
/>
<Typography
variant="h1"
style={{
fontSize: "72px",
fontWeight: 900,
background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
marginBottom: "10px",
textAlign: "center",
}}
>
404
</Typography>
<Typography
variant="h4"
style={{
color: "white",
marginBottom: "8px",
fontWeight: 600,
textAlign: "center",
}}
>
Page Not Found
</Typography>
<Typography
variant="body1"
style={{
color: "#9E9E9E",
marginBottom: "32px",
fontSize: "16px",
textAlign: "center",
maxWidth: "400px",
}}
>
Our code doggo couldn't find the page you were looking for.
</Typography>
<iframe
src="https://giphy.com/embed/FY8c5SKwiNf1EtZKGs"
width="125"
height="165"
frameBorder="0"
className="giphy-embed"
allowFullScreen
style={{
marginBottom: 32,
borderRadius: theme.palette.borderRadius,
}}
/>
<div style={{ width: "100%", maxWidth: "400px" }}>
<Button
variant="contained"
onClick={() => navigate('/docs')}
sx={primaryButtonStyle}
>
Documentation
</Button>
<Button
variant="contained"
onClick={() => navigate('/')}
sx={secondaryButtonStyle}
>
Return Home
</Button>
</div>
</div>
</div>
);
};
export default NotFound;
+1436 -865
View File
File diff suppressed because it is too large Load Diff
+338
View File
@@ -0,0 +1,338 @@
import React, { useEffect, useState } from 'react';
import AdminNavBar from '../components/AdminNavBar.jsx';
import { toast } from "react-toastify";
const Admin2 = (props) => {
// Destructure props if needed
const { userdata, globalUrl, serverside, checkLogin, notifications, setNotifications, stripeKey, isLoaded, isLoggedIn} = props;
const [selectedTab, setSelectedTab] = useState('editdetails');
const [selectedStatus, setSelectedStatus] = React.useState([]);
const [selectedOrganization, setSelectedOrganization] = useState({});
const [organizationFeatures, setOrganizationFeatures] = useState({});
const [orgRequest, setOrgRequest] = React.useState(true);
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const handleGetOrg = (orgId) => {
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 foundorgid = params["org_id"];
if (foundorgid !== undefined && foundorgid !== null) {
orgId = foundorgid;
}
}
console.log("getting organization details for: ", orgId);
// if (orgId === undefined) {
// toast(
// "Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.",
// );
// return;
// }
// Just use this one?
fetch(`${globalUrl}/api/v1/orgs/${orgId}`, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.status === 401) {
}
return response.json();
})
.then((responseJson) => {
if (responseJson["success"] === false) {
toast(
"Failed getting your org. If this persists, please contact support. Redirecting to workflows...",
);
setTimeout(() => {
window.location.href = "/workflows";
}, 3000);
} else {
if (
responseJson.sync_features === undefined ||
responseJson.sync_features === null
) {
responseJson.sync_features = {};
}
if (
responseJson.lead_info !== undefined &&
responseJson.lead_info !== null
) {
var leads = [];
if (responseJson.lead_info.contacted) {
leads.push("contacted");
}
if (responseJson.lead_info.customer) {
leads.push("customer");
}
if (responseJson.lead_info.old_customer) {
leads.push("old customer");
}
if (responseJson.lead_info.old_lead) {
leads.push("old lead");
}
if (responseJson.lead_info.tech_partner) {
leads.push("tech partner");
}
if (responseJson.lead_info.creator) {
leads.push("creator");
}
if (responseJson.lead_info.opensource) {
leads.push("open source");
}
if (responseJson.lead_info.demo_done) {
leads.push("demo done");
}
if (responseJson.lead_info.pov) {
leads.push("pov");
}
if (responseJson.lead_info.lead) {
leads.push("lead");
}
if (responseJson.lead_info.student) {
leads.push("student");
}
if (responseJson.lead_info.internal) {
leads.push("internal");
}
if (responseJson.lead_info.sub_org) {
leads.push("sub_org");
}
setSelectedStatus(leads);
}
setSelectedOrganization(responseJson);
var lists = {
active: {
triggers: [],
features: [],
sync: [],
},
inactive: {
triggers: [],
features: [],
sync: [],
},
};
// FIXME: Set up features
//Object.keys(responseJson.sync_features).map(function(key, index) {
// //console.log(responseJson.sync_features[key])
//})
//setOrgName(responseJson.name)
//setOrgDescription(responseJson.description)
setOrganizationFeatures(lists);
}
})
.catch((error) => {
console.log("Error getting org: ", error);
toast("Error getting current organization");
});
};
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
const foundOrgID = params["org_id"]
useEffect(() => {
if(foundOrgID !== null && foundOrgID !== undefined && userdata?.support && foundOrgID?.length > 0) {
handleClickChangeOrg(foundOrgID)
}
}, [foundOrgID]);
const handleClickChangeOrg = (orgId) => {
// Don't really care about the logout
//name: org.name,
//orgId = "asd"
const data = {
org_id: orgId,
};
localStorage.setItem("globalUrl", "");
localStorage.setItem("getting_started_sidebar", "open");
fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, {
mode: "cors",
credentials: "include",
crossDomain: true,
method: "POST",
body: JSON.stringify(data),
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then(function (response) {
if (response.status !== 200) {
console.log("Error in response");
} else {
localStorage.removeItem("apps")
localStorage.removeItem("workflows")
localStorage.removeItem("userinfo")
}
return response.json();
})
.then(function (responseJson) {
if (responseJson.success === true) {
if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) {
localStorage.setItem("globalUrl", responseJson.region_url)
//globalUrl = responseJson.region_url
}
setTimeout(() => {
window.location.reload()
}, 3000);
toast("Successfully changed active organization - refreshing!");
} else {
if (responseJson.reason !== undefined && responseJson.reason !== null) {
if (!responseJson.reason.includes("already")) {
toast("Failed changing org: " + responseJson.reason);
}
} else {
toast("Failed changing org")
}
}
})
.catch((error) => {
console.log("error changing: ", error);
//removeCookie("session_token", {path: "/"})
});
};
const handleEditOrg = (
name,
description,
orgId,
image,
defaults,
sso_config,
lead_info,
{ mfa_required } = {}
) => {
const data = {
name: name,
description: description,
org_id: orgId,
image: image,
defaults: defaults,
sso_config: sso_config,
lead_info: lead_info,
mfa_required: mfa_required !== undefined ? mfa_required : selectedOrganization?.mfa_required,
};
const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`;
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
crossDomain: true,
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
toast("Failed updating org: ", responseJson.reason);
} else {
if (
lead_info === undefined ||
lead_info === null ||
lead_info === []
) {
toast("Successfully edited org!");
}
}
}),
)
.catch((error) => {
toast("Err: " + error.toString());
});
};
const handleStatusChange = (event) => {
const { value } = event.target;
setSelectedStatus(value);
handleEditOrg(
selectedOrganization?.name,
selectedOrganization?.description,
selectedOrganization.id,
selectedOrganization?.image,
{
app_download_repo: selectedOrganization?.defaults?.app_download_repo,
app_download_branch: selectedOrganization?.defaults?.app_download_branch,
workflow_download_repo: selectedOrganization?.defaults?.workflow_download_repo,
workflow_download_branch: selectedOrganization?.defaults?.workflow_download_branch,
notification_workflow: selectedOrganization?.defaults?.notification_workflow,
documentation_reference: selectedOrganization?.defaults?.documentation_reference,
workflow_upload_repo: selectedOrganization?.defaults?.workflow_upload_repo,
workflow_upload_branch: selectedOrganization?.defaults?.workflow_upload_branch,
workflow_upload_username: selectedOrganization?.defaults?.workflow_upload_username,
workflow_upload_token: selectedOrganization?.defaults?.workflow_upload_token,
newsletter: !selectedOrganization?.defaults?.newsletter,
weekly_recommendations: selectedOrganization?.defaults?.weekly_recommendations,
},
{
sso_entrypoint: selectedOrganization?.sso_config?.sso_entrypoint,
sso_certificate: selectedOrganization?.sso_config?.sso_certificate,
client_id: selectedOrganization?.sso_config?.client_id,
client_secret: selectedOrganization?.sso_config?.client_secret,
openid_authorization: selectedOrganization?.sso_config?.openid_authorization,
openid_token: selectedOrganization?.sso_config?.openid_token,
SSORequired: selectedOrganization?.sso_config?.SSORequired,
auto_provision: selectedOrganization?.sso_config?.auto_provision,
},
value.length === 0 ? ["none"] : value,
);
};
if (
selectedOrganization.id === undefined &&
userdata !== undefined &&
userdata.active_org !== undefined &&
orgRequest
) {
const orgId = userdata.active_org.id
setOrgRequest(false);
handleGetOrg(orgId);
}
return (
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 29, zoom: 0.8, }}>
<AdminNavBar userdata={userdata} isLoaded={isLoaded} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} selectedTab={selectedTab} orgId={selectedOrganization.id} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} setNotifications={setNotifications} stripeKey={stripeKey} notifications={notifications} checkLogin={checkLogin} globalUrl={globalUrl} isCloud={isCloud} serverside={serverside} />
</div>
);
};
export default Admin2;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+142 -111
View File
@@ -37,14 +37,15 @@ import {
AttachFile as AttachFileIcon,
Apps as AppsIcon,
ErrorOutline as ErrorOutlineIcon,
AddAPhoto as AddAPhotoIcon,
AddAPhotoOutlined as AddAPhotoOutlinedIcon,
ZoomInOutlined as ZoomInOutlinedIcon,
ZoomOutOutlined as ZoomOutOutlinedIcon,
Loop as LoopIcon,
AddPhotoAlternate as AddPhotoAlternateIcon,
CallMerge as CallMergeIcon,
AddAPhoto as AddAPhotoIcon,
AddAPhotoOutlined as AddAPhotoOutlinedIcon,
ZoomInOutlined as ZoomInOutlinedIcon,
ZoomOutOutlined as ZoomOutOutlinedIcon,
Loop as LoopIcon,
AddPhotoAlternate as AddPhotoAlternateIcon,
CallMerge as CallMergeIcon,
CloudDownload as CloudDownloadIcon,
OpenInNew as OpenInNewIcon,
} from "@mui/icons-material";
import { v4 as uuidv4 } from "uuid";
@@ -228,6 +229,8 @@ const parseCurl = (s) => {
};
// Basically CRUD for each category + special
// These are already tracked in the shuffle/shuffle-shared/blobs.go file
// as backend should be used for managing this long-term
export const appCategories = [
{
"name": "Communication",
@@ -542,7 +545,7 @@ const AppCreator = (defaultprops) => {
};
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
useEffect(() => {
if (window.location.pathname.includes("apps/edit")) {
@@ -895,8 +898,31 @@ const AppCreator = (defaultprops) => {
};
if (methodvalue["x-label"] !== undefined && methodvalue["x-label"] !== null) {
console.log("LABEL: ", methodvalue["x-label"])
var correctlabel = ""
const labels = methodvalue["x-label"].split(",")
for (let labelkey in labels) {
var label = labels[labelkey].trim()
if (label.toLowerCase() === "no label") {
continue
}
// Remove quotes and escapes
label = label.replace(/['"]+/g, '')
label = label.replace(/\\/g, '')
//label = label.replace("_", " ", -1)
//label = label.charAt(0).toUpperCase() + label.slice(1)
correctlabel = label
break
}
console.log("LABEL: ", correctlabel)
// FIX: Map labels only if they're actually in the category list
newaction.action_label = methodvalue["x-label"]
//newaction.action_label = methodvalue["x-label"]
newaction.action_label = correctlabel
}
if (methodvalue["x-required-fields"] !== undefined && methodvalue["x-required-fields"] !== null) {
@@ -3122,15 +3148,14 @@ const AppCreator = (defaultprops) => {
Scopes for Oauth2
</Typography>
<MuiChipsInput
style={{border: "2px solid #f86a3e", borderRadius: theme.palette.borderRadius,}}
required
required
InputProps={{
style: {
color: "white",
maxHeight: 50,
maxHeight: 160,
},
}}
style={{ maxHeight: 80, overflowX: "hidden", overflowY: "auto" }}
style={{ minHeight: 160, maxHeight: 160, overflowX: "hidden", overflowY: "auto" }}
placeholder="Available Oauth2 Scopes"
color="primary"
fullWidth
@@ -3148,7 +3173,7 @@ const AppCreator = (defaultprops) => {
<div style={{ color: "white", marginTop: 20 }}>
<Typography variant="body1">API key authentication</Typography>
<Typography variant="body2" color="textSecondary">
Add the name of the field used for authentication, e.g. "X-APIKEY". Should NOT be your actual API-key.
<b>Do NOT put your actual API-key.</b> Add the name of the field used for authentication, e.g. "X-APIKEY".
</Typography>
<div style={{display: "flex", marginTop: 10, }}>
<div style={{flex: 4,}}>
@@ -3157,7 +3182,7 @@ const AppCreator = (defaultprops) => {
required
style={{ marginTop: 0, backgroundColor: inputColor }}
fullWidth={true}
placeholder="Field Name (key, NOT your actual API-key)"
placeholder="The Key to use as the header/query - NOT your actual API-key"
type="name"
id="standard-required"
margin="normal"
@@ -3824,7 +3849,7 @@ const AppCreator = (defaultprops) => {
required
style={{
flex: "1",
marginTop: "5px",
marginTop: 5,
marginRight: "15px",
backgroundColor: inputColor,
}}
@@ -4317,7 +4342,8 @@ const AppCreator = (defaultprops) => {
>
<ErrorOutlineIcon />
</Tooltip>
) : (
) : null
{/*
<Tooltip
color="secondary"
title={data.errors.join("\n")}
@@ -4325,7 +4351,7 @@ const AppCreator = (defaultprops) => {
>
<CheckCircleIcon style={{ marginTop: 6 }} />
</Tooltip>
);
*/}
var bgColor = "#61afee";
if (data.method === "POST") {
@@ -4439,7 +4465,7 @@ const AppCreator = (defaultprops) => {
setUpdate(Math.random())
}
}}
value={data.action_label}
value={data?.action_label?.replace(" ", "_").toLowerCase()}
style={{
border: data.action_label === undefined || data.action_label === "No Label" ? "" : `2px solid ${bgColor}`,
borderRadius: theme.shape.borderRadius,
@@ -4461,7 +4487,7 @@ const AppCreator = (defaultprops) => {
return (
<MenuItem
key={labelindex}
value={label}
value={label.replace(" ", "_").toLowerCase()}
style={{
}}
>
@@ -4693,7 +4719,7 @@ const AppCreator = (defaultprops) => {
<TextField
style={{
backgroundColor: inputColor,
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
}}
InputProps={{
style: {
@@ -5042,7 +5068,7 @@ const AppCreator = (defaultprops) => {
<TextField
style={{
backgroundColor: inputColor,
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
}}
InputProps={{
style: {
@@ -5082,7 +5108,7 @@ const AppCreator = (defaultprops) => {
<TextField
style={{
backgroundColor: inputColor,
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
}}
InputProps={{
style: {
@@ -5776,19 +5802,35 @@ const AppCreator = (defaultprops) => {
General information
</h2>
</div>
<div style={{flex: 1, itemAlign: "right", textAlign: "right",}}>
<Tooltip title="Merge with another API (coming soon)" placement="bottom">
<IconButton
disabled
<div style={{flex: 1, itemAlign: "right", textAlign: "right", marginRight: 10, }}>
<Tooltip title="Try the API" placement="bottom">
<Button
variant="outlined"
color="secondary"
onClick={() => {
setOpenApiModal(true)
var urlParams = new URLSearchParams(window.location.search);
if (!urlParams.has("id")) {
window.open(`/apis/${app.id}`, "_blank")
} else {
toast.error("Build the app first.")
}
}}
>
<OpenInNewIcon style={{marginRight: 5, }}/>
Try the API
</Button>
</Tooltip>
<Tooltip title="Manage forks and Merge with another API (coming soon)" placement="bottom">
<IconButton
onClick={() => {
//setOpenApiModal(true)
toast.info("Action merging & fork management coming soon")
}}
style={{marginLeft: 10, }}
>
<CallMergeIcon
style={{}}
onClick={() => {
setOpenApiModal(true)
}}
/>
</IconButton>
</Tooltip>
@@ -5815,18 +5857,13 @@ const AppCreator = (defaultprops) => {
flex: "1",
margin: 10,
border: "1px solid #f85a3e",
borderRadius: theme.palette?.borderRadius,
cursor: "pointer",
backgroundColor: inputColor,
maxWidth: 174,
maxHeight: 174,
}}
onClick={() => {
/*
if (fileBase64.length === 0) {
upload.click()
}
*/
setOpenImageModal(true);
}}
>
@@ -5839,7 +5876,7 @@ const AppCreator = (defaultprops) => {
/>
</div>
</Tooltip>
<div style={{ flex: "3", color: "white" }}>
<div style={{ flex: "3", color: "white", marginLeft: 20, }}>
<div style={{ marginTop: "10px" }} />
Name
<TextField
@@ -5893,35 +5930,28 @@ const AppCreator = (defaultprops) => {
},
}}
/>
<div style={{ marginTop: "10px" }} />
<div style={{ marginTop: 10, }} />
Description
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
paddingTop: 5,
marginTop: 5,
marginRight: 15,
backgroundColor: inputColor,
maxHeight: 250,
overflow: "auto",
maxHeight: 250,
overflowY: "auto"
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
multiline
multiline
variant="outlined"
placeholder="A description for the service"
value={description}
onChange={(e) => setDescription(e.target.value)}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
defaultValue={description}
//onChange={(e) => setDescription(e.target.value)}
onBlur={(e) => setDescription(e.target.value)}
/>
</div>
</div>
@@ -5994,7 +6024,7 @@ const AppCreator = (defaultprops) => {
setBaseUrl(tmpstring);
}}
/>
<div style={{padding: 25, border: "2px solid rgba(255,255,255,0.7)", borderRadius: theme.palette.borderRadius, }}>
<div style={{padding: 25, border: "2px solid rgba(255,255,255,0.7)", borderRadius: theme.palette?.borderRadius, }}>
<span style={{display: "flex", }}>
<FormControl style={{ }} variant="outlined">
<Typography variant="h6">Authentication</Typography>
@@ -6161,62 +6191,63 @@ const AppCreator = (defaultprops) => {
{testView}
*/}
<div style={{display: "flex", marginTop: 35, }}>
{appDownloadData.length > 0 ?
<Tooltip title="Download the OpenAPI specification for the App" placement="bottom">
<IconButton
style={{marginRight: 25, }}
onClick={() => {
toast(`Downloading OpenAPI JSON data for for ${name}`)
// Download as file
var blob = new Blob([appDownloadData], {
type: "application/octet-stream",
});
<div style={{height: 50, padding: 25, display: "flex", marginTop: 35, position: "fixed", bottom: 0, left: 0, width: "100%", backgroundColor: theme.palette?.backgroundColor, borderTop: "1px solid rgba(255,255,255,0.3)",}}>
<div style={{width: 450, margin: "auto", display: "flex", textAlign: "center", }}>
{appDownloadData.length > 0 ?
<Tooltip title="Download the OpenAPI specification for the App" placement="bottom">
<IconButton
style={{marginRight: 25, }}
onClick={() => {
toast(`Downloading OpenAPI JSON data for for ${name}`)
// Download as file
var blob = new Blob([appDownloadData], {
type: "application/octet-stream",
});
var url = URL.createObjectURL(blob);
var link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", `${name}.json`);
var event = document.createEvent("MouseEvents");
event.initMouseEvent(
"click",
true,
true,
window,
1,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null
);
link.dispatchEvent(event);
}}
>
<CloudDownloadIcon />
</IconButton>
</Tooltip>
: null}
<Button
disabled={appBuilding}
color="primary"
variant="contained"
fullWidth
style={{ height: "50px", flex: 1, }}
onClick={() => {
submitApp();
}}
>
{appBuilding ? <CircularProgress /> : "Save"}
</Button>
{appDownloadData.length > 0 ?
<div style={{width: 50, }}/>
: null}
var url = URL.createObjectURL(blob);
var link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", `${name}.json`);
var event = document.createEvent("MouseEvents");
event.initMouseEvent(
"click",
true,
true,
window,
1,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null
);
link.dispatchEvent(event);
}}
>
<CloudDownloadIcon />
</IconButton>
</Tooltip>
: null}
<Button
disabled={appBuilding}
color="primary"
variant="contained"
style={{ height: 50, flex: 1, minWidth: 400, maxWidth: 400, }}
onClick={() => {
submitApp();
}}
>
{appBuilding ? <CircularProgress /> : "Save API"}
</Button>
{appDownloadData.length > 0 ?
<div style={{width: 50, }}/>
: null}
</div>
</div>
<Typography style={{ marginTop: 25, textAlign: "center", }}>
+122 -80
View File
@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useContext, memo } from "react";
import { useInterval } from "react-powerhooks";
import theme from '../theme.jsx';
@@ -49,6 +49,7 @@ import {
Folder as FolderIcon,
LibraryBooks as LibraryBooksIcon,
} from "@mui/icons-material";
import { Context } from "../context/ContextApi.jsx";
import {
ForkRight as ForkRightIcon,
@@ -280,6 +281,7 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52
const Apps = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata, serverside, } = props;
//const [workflows, setWorkflows] = React.useState([]);
const baseRepository = "https://github.com/frikky/shuffle-apps";
//const alert = useAlert();
@@ -334,6 +336,11 @@ const Apps = (props) => {
},
});
useEffect(() => {
console.log("APPVALID: ", appValidation)
redirectOpenApi()
}, [appValidation])
const getUserProfile = (username) => {
if (serverside === true || !isCloud) {
setCreatorProfile({})
@@ -418,7 +425,7 @@ const Apps = (props) => {
minWidth: "100%",
maxWidth: 612.5,
marginBottom: 5,
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
color: "white",
backgroundColor: surfaceColor,
cursor: "pointer",
@@ -870,7 +877,7 @@ const Apps = (props) => {
minWidth: viewWidth,
maxWidth: viewWidth,
color: "white",
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
backgroundColor: surfaceColor,
//display: "flex",
marginBottom: 10,
@@ -955,7 +962,6 @@ const Apps = (props) => {
</Link>
: null
console.log("Sharing config: ", sharingConfiguration);
const activateButton =
selectedApp.generated && !selectedApp.activated ? (
<div>
@@ -1281,10 +1287,15 @@ const Apps = (props) => {
{isCloud && !internalIds.includes(selectedApp.name.toLowerCase()) ?
<Tooltip title="Deactivates this app for the current organisation. This means the app will not be usable again until you re-activate it." placement="top">
<Button
variant="contained"
variant={selectedApp.reference_org === userdata.active_org.id ? "outlined" : "contained"}
component="label"
color="primary"
onClick={() => {
if (selectedApp.reference_org === userdata.active_org.id) {
toast.info("Can't deactivate apps made in this org. Please contact support if you want to deactivate this app.")
return
}
const tmpurl = new URL(window.location.href);
const searchParams = tmpurl.searchParams;
const queryID = searchParams.get("queryID");
@@ -1316,7 +1327,7 @@ const Apps = (props) => {
console.log("No query to handle when activating");
}
activateApp(selectedApp.id, true)
activateApp(selectedApp.id, true, true)
}}
style={{ height: 35, marginTop: 0, marginLeft: 10, }}
>
@@ -1529,22 +1540,36 @@ const Apps = (props) => {
const [hover, setHover] = React.useState(false);
const makeFancy = text?.includes("Generate")
var parsedStyle = {
flex: 1,
padding: 15,
margin: 10,
paddingTop: 25,
backgroundColor: hover ? theme.palette.surfaceColor : "transparent",
cursor: hover ? "pointer" : "default",
textAlign: "center",
minHeight: 150,
maxHeight: 150,
borderRadius: theme.palette?.borderRadius,
}
if (!makeFancy) {
parsedStyle.border = hover ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)"
} else {
parsedStyle.border = "1px solid transparent"
parsedStyle.borderImage = "linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet) 1"
parsedStyle.borderRadius = 0 // This doesn't work. Try to hover with a high one, and it's weird due to borderImage
}
return (
<Paper
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
onClick={func}
style={{
flex: 1,
padding: 15,
margin: 10,
backgroundColor: hover ? theme.palette.surfaceColor : "transparent",
border: hover ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)",
cursor: hover ? "pointer" : "default",
textAlign: "center",
minHeight: 150,
maxHeight: 150,
}}
style={parsedStyle}
>
{icon}
<Typography>
@@ -1565,7 +1590,7 @@ const Apps = (props) => {
</h2>
<div style={{display: "flex"}}>
<AppCreateButton
text="Generate from OpenAPI/Swagger"
text="Upload OpenAPI or Swagger"
func={() => {
setOpenApiModal(true)
}}
@@ -1806,7 +1831,7 @@ const Apps = (props) => {
}}>
<TextField
fullWidth
style={{ backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius, maxWidth: leftBarSize - 20, }}
style={{ backgroundColor: theme.palette.inputColor, borderRadius: theme.palette?.borderRadius, maxWidth: leftBarSize - 20, }}
InputProps={{
style: {
color: "white",
@@ -1845,9 +1870,12 @@ const Apps = (props) => {
)
}
const activateApp = (appid, refresh) => {
const activateApp = (appid, refresh, deactivate) => {
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`
const url = deactivate === true ?
`${globalUrl}/api/v1/apps/${appid}/deactivate`
:
appExists ? `${globalUrl}/api/v1/apps/${appid}/deactivate` : `${globalUrl}/api/v1/apps/${appid}/activate`
fetch(url, {
method: 'GET',
@@ -1876,7 +1904,7 @@ const Apps = (props) => {
if (appExists) {
toast("App deactivated for your organization! Existing workflows with the app will continue to work.")
} else {
toast("App activated for your organization!")
toast("App activation changed for your organization!")
}
if (refresh === true) {
@@ -2021,8 +2049,6 @@ const Apps = (props) => {
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
}
])
} else {
console.log("No query to handle when activating")
}
activateApp(hit.objectID, true)
@@ -2161,7 +2187,7 @@ const Apps = (props) => {
</div>
<div style={{ height: 50 }}>
<TextField
style={{ backgroundColor: inputColor, borderRadius: theme.palette.borderRadius, }}
style={{ backgroundColor: inputColor, borderRadius: theme.palette?.borderRadius, }}
InputProps={{
style: {
},
@@ -2212,7 +2238,7 @@ const Apps = (props) => {
minWidth: viewWidth,
maxWidth: viewWidth,
color: "white",
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
//display: "flex",
marginBottom: 10,
overflow: "hidden",
@@ -2349,6 +2375,7 @@ const Apps = (props) => {
toast("Hotloading apps from location in .env");
setIsLoading(true);
fetch(globalUrl + "/api/v1/apps/run_hotload", {
method: "POST",
mode: "cors",
headers: {
Accept: "application/json",
@@ -2556,7 +2583,8 @@ const Apps = (props) => {
}
//fetch("http://localhost:8080/doc_to_openapi", {
fetch("https://doc-to-openapi-stbuwivzoq-nw.a.run.app/doc_to_openapi", {
//fetch("https://doc-to-openapi-stbuwivzoq-nw.a.run.app/doc_to_openapi", {
fetch("https://doc-to-openapi-stbuwivzoq-nw.a.run.app/api/v1/doc_to_openapi", {
method: "POST",
headers: {
"Accept": "application/json",
@@ -2576,12 +2604,14 @@ const Apps = (props) => {
})
.then((responseJson) => {
// Check if openapi or swagger in string of the json
var parsedtext = responseJson
try {
const parsedtext = JSON.stringify(responseJson);
parsedtext = JSON.stringify(responseJson);
if (parsedtext.indexOf("openapi") === -1 && parsedtext.indexOf("swagger") === -1) {
setValidation(false);
setOpenApiError("Error in generation: "+parsedtext);
return;
setValidation(false)
setOpenApiError("Error in generation: "+parsedtext)
return
}
} catch (e) {
setValidation(false);
@@ -2590,7 +2620,7 @@ const Apps = (props) => {
}
console.log("Validating response!");
validateOpenApi(responseJson);
validateOpenApi(parsedtext)
})
.catch((error) => {
setValidation(false);
@@ -2679,10 +2709,11 @@ const Apps = (props) => {
body: openApidata,
credentials: "include",
})
.then((response) => {
.then((response) => {
setValidation(false);
return response.json();
})
})
.then((responseJson) => {
if (responseJson.success) {
setAppValidation(responseJson.id);
@@ -2701,8 +2732,16 @@ const Apps = (props) => {
};
const redirectOpenApi = () => {
if (appValidation === undefined || appValidation === null || appValidation.length === 0) {
return
}
toast.success("Successfully validated OpenAPI. Redirecting to app creation. Remember to save the app to be able to use it.", {
// Disable autoclose
autoClose: 10000,
})
navigate(`/apps/new?id=${appValidation}`)
};
}
const handleGithubValidation = (forceUpdate) => {
getSpecificApps(openApi, forceUpdate);
@@ -2962,10 +3001,12 @@ const Apps = (props) => {
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
backgroundColor: theme.palette.platformColor,
borderRadius: theme.palette?.borderRadius,
color: "white",
minWidth: "800px",
minHeight: "320px",
padding: 50,
},
}}
>
@@ -2997,14 +3038,14 @@ const Apps = (props) => {
height: "50px",
}}
variant="contained"
disabled={openApi.length === 0 || appValidation.length > 0}
disabled={openApi.length === 0 || appValidation.length > 0 || validation}
color="primary"
onClick={() => {
setOpenApiError("");
validateDocumentationUrl();
}}
>
Validate
Generate
</Button>
),
}}
@@ -3019,22 +3060,6 @@ const Apps = (props) => {
placeholder="API Documentation URL"
fullWidth
/>
<p>Or upload document with the content (coming soon)</p>
<input
hidden
type="file"
ref={upload}
multiple={false}
onChange={uploadFileDocumentation}
/>
<Button
variant="contained"
color="primary"
disabled
onClick={() => upload.current.click()}
>
Upload
</Button>
{errorText}
</DialogContent>
<DialogActions>
@@ -3053,17 +3078,6 @@ const Apps = (props) => {
>
Cancel
</Button>
<Button
variant="contained"
style={{ borderRadius: "0px" }}
disabled={appValidation.length === 0}
onClick={() => {
redirectOpenApi();
}}
color="primary"
>
Continue
</Button>
</DialogActions>
</FormControl>
</Dialog>
@@ -3079,10 +3093,12 @@ const Apps = (props) => {
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
backgroundColor: theme.palette.platformColor,
borderRadius: theme.palette?.borderRadius,
color: "white",
minWidth: "800px",
minHeight: "320px",
padding: 50,
},
}}
>
@@ -3189,22 +3205,48 @@ const Apps = (props) => {
</Dialog>
) : null;
const loadedCheck =
isLoaded && !firstrequest ? (
<div>
{appView}
{modalView}
{publishModal}
{generateAppView}
{appsModalLoad}
{deleteModal}
</div>
) : (
<div></div>
);
const loadedCheck = isLoaded && !firstrequest ? (
<SidebarAdjustWrapper userdata={userdata}>
<AppsWrapper
userdata={userdata}
appView={appView}
modalView={modalView}
publishModal={publishModal}
generateAppView={generateAppView}
appsModalLoad={appsModalLoad}
deleteModal={deleteModal}
/>
</SidebarAdjustWrapper>
) : (
<div></div>
);
// Maybe use gridview or something, idk
return loadedCheck;
};
export default Apps;
const AppsWrapper = memo(({ appView, modalView, userdata, publishModal, generateAppView, appsModalLoad, deleteModal }) => (
<>
{appView}
{modalView}
{publishModal}
{generateAppView}
{appsModalLoad}
{deleteModal}
</>
));
const SidebarAdjustWrapper = memo(({ children }) => {
const {leftSideBarOpenByClick } = useContext(Context)
return (
<div style={{ marginLeft: leftSideBarOpenByClick ? 250 : 80, transition: 'margin-left 0.3s ease' }}>
{children}
</div>
);
});
File diff suppressed because it is too large Load Diff
+467
View File
@@ -0,0 +1,467 @@
import React, { useState, useRef, useEffect } from 'react';
import { toast } from 'react-toastify';
import { Button, Box, Typography, Paper, Toolbar, Divider, CircularProgress } from '@mui/material';
import {
PlayArrow as PlayArrowIcon,
Save as SaveIcon,
History as HistoryIcon
} from '@mui/icons-material';
import ExecutionPanel from '../components/ExecutionPanel.jsx';
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
const CodeWorkflow = (defaultprops) => {
const { serverside, userdata, globalUrl, isLoaded, isLoggedIn, surfaceColor, inputColor, ...props } = defaultprops;
const [workflow, setWorkflow] = useState({});
const [showExecutions, setShowExecutions] = useState(false);
// In CodeWorkflow, add this state
const [panelHeight, setPanelHeight] = useState(400);
const [executions, setExecutions] = useState([]);
const [currentExecution, setCurrentExecution] = useState(null);
const [mainAction, setMainAction] = useState(null);
const editorRef = useRef(null);
const [apiKey, setApiKey] = useState("");
const [editorData, setEditorData] = React.useState({
"name": "",
"value": "",
"field_number": -1,
"actionlist": [],
"field_id": "",
})
const getSettings = async () => {
try {
const response = await fetch(`${globalUrl}/api/v1/getsettings`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
});
if (response.status !== 200) {
console.log("Status not 200 for getsettings :O!");
if (response.status >= 500) {
toast("Something went wrong while loading the settings. Please reload.")
}
return null;
}
const responseJson = await response.json();
setApiKey(responseJson.apikey);
console.log("API Key: ", responseJson.apikey);
return responseJson.apikey;
} catch (error) {
console.log("Get settings error: ", error.toString());
return null;
}
}
// Calculate editor height based on execution panel visibility
const getEditorHeight = () => {
return `calc(100vh - ${showExecutions ? `${panelHeight + 40}px` : '40px'})`;
};
let url = window.location.pathname;
const workflowId = url.split("/")[2];
const getWorkflow = async (workflow_id, sourcenode) => {
try {
const response = await fetch(`${globalUrl}/api/v1/workflows/${workflow_id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
});
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!");
if (response.status >= 500) {
toast("Something went wrong while loading the workflow. Please reload.")
}
}
const responseJson = await response.json();
setWorkflow(responseJson);
for (let i = 0; i < responseJson.actions.length; i++) {
if (responseJson.actions[i].app_id === "3e320a20966d33c9b7e6790b2705f0bf") {
console.log("Setting code to: ", responseJson.actions[i].parameters[0].value);
setCode(responseJson.actions[i].parameters[0].value);
setMainAction(responseJson.actions[i]);
if (responseJson.actions[i].parameters[0].value.length === 0) {
// fetch API key of the user
const result = await getSettings();
console.log("accessible result: ", result);
// await setCode(`
// from shufflepy import Shuffle
// shuffle = Shuffle(
// "${result}",
// url='https://shuffler.io',
// )
// `
// );
}
break;
}
}
} catch (error) {
console.log("Get workflows error: ", error.toString());
}
};
useEffect(() => {
getWorkflow(workflowId);
}, []);
// In CodeWorkflow component, add this effect:
useEffect(() => {
if (workflow.id) {
// Check URL for execution_id parameter
const urlParams = new URLSearchParams(window.location.search);
const executionId = urlParams.get('execution_id');
if (executionId) {
setShowExecutions(true); // Show the panel if execution_id is present
}
}
}, [workflow]);
const [code, setCode] = useState("");
const [testResult, setTestResult] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const saveLatestWorkflow = () => {
let newWorkflow = workflow;
console.log("Workflow actions: ", newWorkflow.actions);
// find the latest "Shuffle tools fork" node
for (let i = 0; i < newWorkflow.actions.length; i++) {
console.log("Actios: ", newWorkflow.actions[i]);
if (newWorkflow.actions[i].app_id === "3e320a20966d33c9b7e6790b2705f0bf") {
// update the code
console.log("Updating code: ", code);
newWorkflow.actions[i].parameters[0].value = code;
break;
}
}
fetch(`${globalUrl}/api/v1/workflows/${workflow.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify(newWorkflow),
}).then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!");
toast("Something went wrong while saving the workflow. Please try again.", { type: "error" });
} else if (response.status === 200) {
toast("Workflow saved successfully!");
}
});
};
const getParents = async () => {
return [
{
"label": "Execution Argument",
"type": "INTERNAL"
}
]
}
const handleRunCode = async () => {
saveLatestWorkflow();
setLoading(true);
setError("");
let start_node = "";
for (let i = 0; i < workflow.actions.length; i++) {
if (workflow.actions[i].isStartNode) {
start_node = workflow.actions[i].id;
}
}
try {
const response = await fetch(`${globalUrl}/api/v1/workflows/${workflow.id}/execute`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify({
"start": start_node,
"execution_arguments": "",
}),
});
if (response.ok) {
const responseJson = await response.json();
const newExecution = {
execution_id: responseJson.execution_id,
authorization: responseJson.authorization,
status: 'EXECUTING',
started_at: new Date().toISOString()
};
setShowExecutions(true);
setCurrentExecution(newExecution);
}
} catch (error) {
console.log("Error: ", error);
setError(error.toString());
} finally {
setLoading(false);
}
};
const handleSaveCode = async () => {
saveLatestWorkflow();
};
const handleEditorDidMount = (editor, monaco) => {
if (monaco.languages && monaco.languages.python) {
if (monaco.languages.python.pythonDefaults) {
monaco.languages.python.pythonDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true
});
}
}
monaco.languages.setLanguageConfiguration('python', {
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
]
});
};
const handleEditorChange = (value) => {
setCode(value);
};
return (
<Box sx={{
display: 'flex',
flexDirection: 'column',
height: '100vh', // Set to full viewport height
backgroundColor: '#252526',
marginLeft: '100px',
}}>
{/* IDE-like toolbar */}
<Toolbar
variant="dense"
sx={{
backgroundColor: '#333333',
borderBottom: '1px solid #454545',
minHeight: '40px',
px: 1,
flex: '0 0 auto' // Prevent toolbar from flexing
}}
>
<Typography
variant="subtitle2"
sx={{
color: '#CCCCCC',
marginRight: 2
}}
>
{workflow.name || 'Untitled Workflow'}
</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
size="small"
variant="contained"
onClick={handleRunCode}
disabled={loading}
startIcon={<PlayArrowIcon />}
sx={{
backgroundColor: '#0098FF',
'&:hover': {
backgroundColor: '#0076CE'
},
textTransform: 'none',
minWidth: '80px'
}}
>
{loading ? 'Running...' : 'Run'}
</Button>
<Button
size="small"
variant="outlined"
startIcon={<HistoryIcon />}
onClick={() => setShowExecutions(true)}
sx={{
color: '#CCCCCC',
borderColor: '#454545',
'&:hover': {
borderColor: '#666666',
backgroundColor: 'rgba(255,255,255,0.1)'
},
textTransform: 'none'
}}
>
Show Executions
</Button>
<Button
size="small"
variant="outlined"
onClick={handleSaveCode}
startIcon={<SaveIcon />}
sx={{
color: '#CCCCCC',
borderColor: '#454545',
'&:hover': {
borderColor: '#666666',
backgroundColor: 'rgba(255,255,255,0.1)'
},
textTransform: 'none',
marginLeft: 1
}}
>
Save
</Button>
</Box>
</Toolbar>
{/* Editor container */}
<Box sx={{
height: getEditorHeight(),
// flex: '1 1 auto', // Allow editor to grow and shrink
// minHeight: 0, // Important for flex child
transition: 'height 0.2s ease-in-out',
position: 'relative' // Needed for Editor's absolute positioning
}}>
{workflow && mainAction ? (
<ShuffleCodeEditor
expansionModalOpen={true}
setExpansionModalOpen={true}
isCloud={true}
globalUrl={globalUrl}
workflowExecutions={executions}
getParents={getParents}
selectedAction={mainAction}
aiSubmit={() => { }}
toolsAppId={mainAction.app_id}
codedata={code}
setcodedata={setCode}
parameterName={editorData.name}
fieldCount={editorData.field_number}
actionlist={editorData.actionlist}
fieldname={editorData.field_id}
changeActionParameterCodeMirror={() => { }}
activeDialog={() => { }}
setActiveDialog={() => { }}
fullScreenMode={true}
/>
) : (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
<CircularProgress />
</Box>
)}
{/* <Editor
height="100%" // Take full height of container
language="python"
theme="vs-dark"
value={code}
onChange={handleEditorChange}
onMount={handleEditorDidMount}
options={{
minimap: { enabled: false },
fontSize: 14,
lineHeight: 24,
fontFamily: "'JetBrains Mono', Consolas, monospace",
fontLigatures: true,
scrollBeyondLastLine: false,
automaticLayout: true,
contextmenu: false,
wordWrap: 'on',
snippetSuggestions: 'top',
quickSuggestions: {
other: true,
comments: false,
strings: false
},
intellisenseOptions: {
includeAutoCompletionsFromLanguageFile: true,
maxCompletionItems: 10
}
}}
/> */}
</Box>
{/* Create an input element called "copy_element_shuffle" that is not visible */}
<input type="text" id="copy_element_shuffle" style={{ position: "absolute", left: "-9999px" }} />
{/* Results Panel */}
{(error || testResult) && (
<Box sx={{
height: '200px',
backgroundColor: '#1E1E1E',
borderTop: '1px solid #454545',
overflow: 'auto',
flex: '0 0 auto' // Prevent panel from flexing
}}>
{error && (
<Box sx={{ p: 2, color: '#f44336' }}>
<Typography variant="subtitle2">Error:</Typography>
<pre style={{ margin: 0 }}>{error}</pre>
</Box>
)}
{testResult && (
<Box sx={{ p: 2, color: '#4caf50' }}>
<Typography variant="subtitle2">Test Result:</Typography>
<pre style={{ margin: 0 }}>{JSON.stringify(testResult, null, 2)}</pre>
</Box>
)}
</Box>
)}
{/* In CodeWorkflow component */}
{showExecutions && (
<ExecutionPanel
workflow={workflow}
globalUrl={globalUrl}
onClose={() => setShowExecutions(false)}
currentExecution={currentExecution}
height={panelHeight}
onHeightChange={setPanelHeight} // Add this prop to handle height updates
mainAction={mainAction}
/>
)}
</Box>
);
};
export default CodeWorkflow;
+169 -48
View File
@@ -11,6 +11,7 @@ import { useNavigate, Link, useParams } from "react-router-dom";
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
import Draggable from "react-draggable";
import DashboardBarchart, { LoadStats } from '../components/DashboardBarchart.jsx';
import {
Autocomplete,
@@ -18,6 +19,8 @@ import {
TextField,
IconButton,
Button,
Select,
MenuItem,
Typography,
Grid,
Paper,
@@ -146,28 +149,8 @@ const inputdata = [
const LineChartWrapper = ({keys, height, width}) => {
const [hovered, setHovered] = useState("");
//console.log("Date: ", new Date("2019-11-14T08:00:00.000Z"))
console.log("Keys: ", keys)
var inputdata = keys.data
/*
const inputdata = [{
"key": "Intel",
"data": [
{ key: new Date('11/22/2019'), data: 3, metadata: {color: "orange", "name": "Intel"}},
{ key: new Date('11/24/2019'), data: 8, metadata: {color: "orange", "name": "Intel"}},
{ key: new Date('11/29/2019'), data: 2, metadata: {color: "orange", "name": "Intel"}},
]},
{
"key": "Popper",
"data": [
{ key: new Date('11/24/2019'), data: 9, },
{ key: new Date('11/29/2019'), data: 3, },
]
}
]
*/
return (
<div style={{}}>
<Typography variant="h6" style={{marginBotton: 15}}>
@@ -211,11 +194,10 @@ const LineChartWrapper = ({keys, height, width}) => {
offset: '5px, 5px'
}}
content={(data, color) => {
console.log("DATA: ", data)
const name = data.metadata !== undefined && data.metadata.name !== undefined ? data.metadata.name : "No"
return (
<div style={{borderRadius: theme.palette.borderRadius, backgroundColor: theme.palette.inputColor, border: "1px solid rgba(255,255,255,0.3)", color: "white", padding: 5, cursor: "pointer",}}>
<div style={{borderRadius: theme.palette?.borderRadius, backgroundColor: theme.palette.inputColor, border: "1px solid rgba(255,255,255,0.3)", color: "white", padding: 5, cursor: "pointer",}}>
<Typography variant="body1">
{name}
</Typography>
@@ -299,7 +281,7 @@ const RadialChart = ({keys, setSelectedCategory}) => {
}}
content={(data, color) => {
return (
<div style={{borderRadius: theme.palette.borderRadius, backgroundColor: theme.palette.inputColor, border: "1px solid rgba(255,255,255,0.3)", color: "white", padding: 5, cursor: "pointer",}}>
<div style={{borderRadius: theme.palette?.borderRadius, backgroundColor: theme.palette.inputColor, border: "1px solid rgba(255,255,255,0.3)", color: "white", padding: 5, cursor: "pointer",}}>
<Typography variant="body1">
{data.x}
</Typography>
@@ -350,12 +332,47 @@ const Dashboard = (props) => {
const [frameworkData, setFrameworkData] = useState(undefined);
const [widgetData, setWidgetData] = useState([]);
const [newWidgetData, setNewWidgetData] = useState([]);
const [, setUpdate] = useState(0);
let navigate = useNavigate();
const isCloud =
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io";
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const path = window.location.pathname
if (path !== undefined && path !== null) {
if (path.includes("/automate")) {
navigate("/usecases")
}
if (path.includes("/security")) {
navigate("/forms")
}
}
useEffect(() => {
// Look for the path. If it is automation, go to usecases
// Handles redirect
const widgetnames = ["app_executions_cloud"]
for (let widgetkey in widgetnames) {
const widgetName = widgetnames[widgetkey]
console.log("NAME: ", widgetName)
const resp = LoadStats(globalUrl, widgetName)
if (resp !== undefined) {
resp.then((data) => {
console.log("Got data in parent: ", data)
if (data === undefined) {
} else {
newWidgetData.push(data)
setNewWidgetData(newWidgetData)
}
})
}
}
}, [])
useEffect(() => {
if (selectedUsecaseCategory.length === 0) {
@@ -368,6 +385,7 @@ const Dashboard = (props) => {
}
}, [selectedUsecaseCategory])
const checkSelectedParams = () => {
const urlSearchParams = new URLSearchParams(window.location.search)
const params = Object.fromEntries(urlSearchParams.entries())
@@ -409,23 +427,22 @@ const Dashboard = (props) => {
}, [usecases])
const getWidget = (dashboard, widget) => {
fetch(`${globalUrl}/api/v1/dashboards/${dashboard}/widgets/${widget}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
fetch(`${globalUrl}/api/v1/dashboards/${dashboard}/widgets/${widget}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
console.log("Resp: ", responseJson)
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
//toast("Failed loading: " + responseJson.reason)
@@ -441,14 +458,12 @@ const Dashboard = (props) => {
}
const foundWidget = widgetData.findIndex(data => data.title === widget)
console.log("Found: ", foundWidget)
if (foundWidget !== undefined && foundWidget !== null && foundWidget >= 0) {
widgetData[foundWidget] = tmpdata
} else {
widgetData.push(tmpdata)
}
console.log("Data: ", widgetData)
setWidgetData(widgetData)
}
})
@@ -480,7 +495,7 @@ const Dashboard = (props) => {
useEffect(() => {
getWidget("main", "Overall")
getWidget("main", "Overall2")
}, []);
}, [])
const fetchdata = (stats_id) => {
fetch(globalUrl + "/api/v1/stats/" + stats_id, {
@@ -646,7 +661,6 @@ const Dashboard = (props) => {
stats["workflow_executions"].data !== undefined
) {
setStatsRan(true);
//console.log("NEW DATA?: ", stats)
console.log("SET WORKFLOW: ", stats["workflow_executions"]);
//var curday = startDate.getDate()
@@ -729,6 +743,100 @@ const Dashboard = (props) => {
</div>
) : null;
const WidgetController = (props) => {
const { data, index, availableStats, } = props
const [hovering, setHovering] = useState(false)
const newname = data.key !== undefined ? data.key.replaceAll("_", " ") : ""
console.log("KEYDATA: ", data)
const loadNewStats = (newkey) => {
const resp = LoadStats(globalUrl, newkey)
if (resp !== undefined) {
resp.then((respdata) => {
if (respdata === undefined || respdata === null) {
toast("Failed to laod data. Please try again, or contact support@shuffler.io if this persists.")
} else {
newWidgetData[index] = respdata
setNewWidgetData(newWidgetData)
setUpdate(Math.random())
}
})
}
}
return (
<Draggable>
<Paper
style={{
height: "100%", width: "100%", maxWidth: 500, margin: 15, padding: "15px 15px 15px 15px", textAlign: "left",
backgroundColor: hovering ? theme.palette.inputColor : theme.palette.backgroundColor,
cursor: hovering ? "pointer" : "default",
}}
onMouseEnter={() => {
setHovering(true)
}}
onMouseLeave={() => {
setHovering(false)
}}
>
<div style={{display: "flex", justifyContent: "space-between", alignItems: "center",}}>
<Typography variant="h6">
{newname}
</Typography>
{data.available_keys === undefined || data.available_keys === null || data.available_keys.length === 0 ? null :
<Select
MenuProps={{
disableScrollLock: true,
}}
labelId="Response Action"
value={data.key}
SelectDisplayProps={{
style: {
},
}}
fullWidth
onChange={(e) => {
loadNewStats(e.target.value)
}}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
height: 40,
maxWidth: 150,
borderRadius: theme.palette?.borderRadius,
}}
>
{data.available_keys.map((foundKey, index) => {
const parsedKeyName = foundKey.replaceAll("_", " ")
return (
<MenuItem
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={foundKey}
>
<em>{parsedKeyName}</em>
</MenuItem>
)
})}
</Select>
}
</div>
<DashboardBarchart
timelineData={data}
height={50}
/>
</Paper>
</Draggable>
)
}
const data = (
<div className="content" style={{width: 1000, margin: "auto", paddingBottom: 200, textAlign: "center",}}>
<div style={{width: 500, margin: "auto"}}>
@@ -739,18 +847,31 @@ const Dashboard = (props) => {
: null}
</div>
{widgetData === undefined || widgetData === null || widgetData === [] || widgetData.length === 0 ? null :
{/*widgetData === undefined || widgetData === null || widgetData === [] || widgetData.length === 0 ? null :
<Draggable>
<Paper style={{height: 350, width: 500, padding: "15px 15px 15px 15px", }}>
<LineChartWrapper keys={widgetData[0]} height={280} width={470} />
</Paper>
</Draggable>
*/}
{newWidgetData === undefined || newWidgetData === null || newWidgetData === [] ? null :
newWidgetData.map((data, index) => {
return (
<WidgetController
key={index}
index={index}
data={data}
/>
)
})
}
</div>
);
const dataWrapper = (
<div style={{ maxWidth: 1366, margin: "auto" }}>{data}</div>
<div style={{ maxWidth: 1366, margin: "auto", paddingTop: 10, }}>{data}</div>
);
return dataWrapper;
+210
View File
@@ -0,0 +1,210 @@
import React, { useState } from "react";
import {
Container,
Box,
TextField,
Switch,
Typography,
Button,
CircularProgress,
Paper,
} from "@mui/material";
import { toast } from "react-toastify";
import theme from '../theme.jsx';
import DetectionRuleCard from "../components/DetectionRuleCard.jsx";
const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl, isTenzirActive) => {
if (!isTenzirActive) {
toast("connect to siem first for global enable/disable to work");
return;
}
const action = folderDisabled ? "enable_folder" : "disable_folder";
const url = `${globalUrl}/api/v1/detection/${action}`;
fetch(url, {
method: "PUT",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === true) {
if (action === "enable_folder") setFolderDisabled(false);
else setFolderDisabled(true);
} else {
//toast(`failed to disable rule`);
}
})
)
.catch((error) => {
console.log(`Error in ${action} the rule: `, error);
toast(`An error occurred while ${action} the rule`);
});
};
const Detection = (props) => {
const { globalUrl, ruleInfo, folderDisabled, setFolderDisabled, isTenzirActive } = props;
const [searchQuery, setSearchQuery] = useState("");
const [loading, setLoading] = useState(false);
const handleConnectClick = () => {
if (!isTenzirActive) {
setLoading(true);
const url = `${globalUrl}/api/v1/detection/siem/connect`;
fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === true) {
setTimeout(() => {
setLoading(false);
window.location.reload();
}, 15000);
} else {
setLoading(false);
toast("Failed to connect to SIEM");
}
})
)
.catch((error) => {
setLoading(false);
console.log(`Error in connecting to SIEM: `, error);
toast("An error occurred while connecting to SIEM");
});
} else {
console.log("Already connected to SIEM");
}
};
const filteredRules = ruleInfo?.filter((rule) =>
rule.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
rule.description.toLowerCase().includes(searchQuery.toLowerCase())
);
return (
<Container>
<Paper
style={{
marginTop: 50,
width: "100%",
padding: 50,
backgroundColor: theme.palette.backgroundColor,
borderRadius: theme.palette.borderRadius,
}}
>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 2,
}}
>
<Typography variant="h6" component="div">
Sigma Detection Rules
</Typography>
<Button
variant="contained"
onClick={handleConnectClick}
disabled={loading} // Disable the button while loading
color={isTenzirActive ? "primary" : "secondary"}
style={{ }}
>
{loading ? <CircularProgress size={24} /> : isTenzirActive ? "Connected to siem" : "Connect to siem"}
</Button>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 2,
}}
>
<Box
sx={{
display: "flex",
}}
>
<TextField
label="Search rules"
variant="outlined"
size="small"
sx={{ mr: 2 }}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
{/* <Button
color="primary"
variant="contained"
onClick={() => uploadRef.current.click()}
>
<PublishIcon /> Upload sigma file
</Button>
<input
hidden
type="file"
multiple
ref={uploadRef}
onChange={(event) => {
uploadFiles(event.target.files);
}}
/> */}
</Box>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Typography variant="body2" sx={{ mr: 1 }}>
Global disable/enable
</Typography>
<Switch
checked={!folderDisabled}
onChange={() =>
handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl, isTenzirActive)
}
disabled={!isTenzirActive}
/>
</Box>
</Box>
<Box
sx={{
height: "500px",
width: "100%",
overflowY: "auto",
p: 1,
}}
>
{filteredRules?.length > 0 ?
filteredRules.map((card) => {
console.log("RULE CARD: ", card);
return (
<DetectionRuleCard
key={card.file_id}
ruleName={card.title}
description={card.description}
file_id={card.file_id}
globalUrl={globalUrl}
folderDisabled={folderDisabled}
isTenzirActive={isTenzirActive}
{...card}
/>
)
})
: null }
</Box>
</Paper>
</Container>
);
};
export default Detection;
+162
View File
@@ -0,0 +1,162 @@
import React, { useState, useEffect } from "react";
import { Container, CircularProgress, Typography } from "@mui/material";
import { toast } from "react-toastify";
import Detection from "./Detection";
const DetectionDashBoard = (props) => {
const { globalUrl } = props;
const [ruleInfo, setRuleInfo] = useState(null);
const [, setSelectedRule] = useState(null);
const [, setFileData] = useState("");
const [isTenzirActive, setIsTenzirActive] = useState(false);
const [folderDisabled, setFolderDisabled] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [importAttempts, setImportAttempts] = useState(0);
const maxImportAttempts = 2;
useEffect(() => {
const fetchTimeout = setTimeout(() => {
fetchSigmaInfo();
}, 1000); // Delay by 1 second
return () => clearTimeout(fetchTimeout);
}, [globalUrl]);
useEffect(() => {
if (ruleInfo && ruleInfo.length === 0 && importAttempts < maxImportAttempts) {
importSigmaFromUrl();
}
}, [ruleInfo]);
const openEditBar = (rule) => {
setSelectedRule(rule);
fetchFileContent(rule.file_id);
};
const handleSave = (updatedContent) => {
toast("This will be saved");
};
const fetchFileContent = (file_id) => {
setFileData("");
fetch(`${globalUrl}/api/v1/files/${file_id}/content`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for file :O!");
return "";
}
return response.text();
})
.then((respdata) => {
if (respdata.length === 0) {
toast("Failed getting file. Is it deleted?");
return;
}
setFileData(respdata);
})
.catch((error) => {
toast(error.toString());
});
};
const fetchSigmaInfo = () => {
const url = `${globalUrl}/api/v1/files/detection/sigma_rules`;
setIsLoading(true);
fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson["success"] === false) {
toast("Failed to get sigma rules");
} else {
setRuleInfo(responseJson.sigma_info || []);
setFolderDisabled(responseJson.folder_disabled);
setIsTenzirActive(responseJson.is_tenzir_active);
}
setIsLoading(false);
})
.catch((error) => {
setIsLoading(false);
console.log("Error in getting sigma files: ", error);
toast("An error occurred while fetching sigma rules");
setRuleInfo([]);
});
};
const importSigmaFromUrl = () => {
setIsLoading(true);
setImportAttempts((prevAttempts) => prevAttempts + 1);
const url = "https://github.com/satti-hari-krishna-reddy/shuffle_sigma";
const folder = "sigma";
const parsedData = {
url: url,
path: folder,
field_3: "main",
};
toast(`Getting files from url ${url}. This may take a while if the repository is large. Please wait...`);
fetch(`${globalUrl}/api/v1/files/download_remote_enhanced`, {
method: "POST",
mode: "cors",
headers: {
Accept: "application/json",
},
body: JSON.stringify(parsedData),
credentials: "include",
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.success) {
toast("Successfully loaded files from " + url);
fetchSigmaInfo(); // Fetch again after successful import
} else {
toast(responseJson.reason ? `Failed loading: ${responseJson.reason}` : "Failed loading");
}
setIsLoading(false);
})
.catch((error) => {
toast(error.toString());
setIsLoading(false);
});
};
if (isLoading && (!ruleInfo || ruleInfo.length === 0)) {
return (
<Container style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }}>
<div>
<CircularProgress />
<Typography variant="h6" style={{ marginTop: 20 }}>Downloading rules, please wait...</Typography>
</div>
</Container>
);
}
return (
<Container style={{ display: "flex" }}>
<Detection
globalUrl={globalUrl}
ruleInfo={ruleInfo}
folderDisabled={folderDisabled}
setFolderDisabled={setFolderDisabled}
isTenzirActive={isTenzirActive}
/>
</Container>
);
};
export default DetectionDashBoard;
+225
View File
@@ -0,0 +1,225 @@
import React, { useState } from "react";
import {
Container,
Box,
TextField,
Switch,
Typography,
Button,
CircularProgress,
Paper,
} from "@mui/material";
import { toast } from "react-toastify";
import theme from '../theme.jsx';
import DetectionRuleCard from "../components/DetectionRuleCard.jsx";
const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl, isTenzirActive) => {
if (!isTenzirActive) {
toast.warn("Connect to siem first for global enable/disable to work");
return;
}
const action = folderDisabled ? "enable_folder" : "disable_folder";
const url = `${globalUrl}/api/v1/detections/${action}`;
fetch(url, {
method: "PUT",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === true) {
if (action === "enable_folder") setFolderDisabled(false);
else setFolderDisabled(true);
} else {
//toast(`failed to disable rule`);
}
})
)
.catch((error) => {
console.log(`Error in ${action} the rule: `, error);
toast(`An error occurred while ${action} the rule`);
});
};
const Detection = (props) => {
const { globalUrl, ruleInfo, folderDisabled, setFolderDisabled, isTenzirActive, detectionInfo } = props;
const [searchQuery, setSearchQuery] = useState("");
const [loading, setLoading] = useState(false);
const handleConnectClick = () => {
if (isTenzirActive) {
return
}
if (detectionInfo.category === undefined || detectionInfo.category === null) {
toast.warn("Detection category not found. Please try again or contact support@shuffler.io if you think this is a bug.")
return
}
setLoading(true);
const url = `${globalUrl}/api/v1/detections/${detectionInfo?.category}/connect`;
fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === true) {
setTimeout(() => {
setLoading(false);
window.location.reload();
}, 15000);
} else {
if (responseJson.reason !== undefined && responseJson.reason !== null) {
toast(responseJson.reason)
} else {
toast(`Failed to connect to ${detectionInfo?.category}`);
}
if (responseJson.action !== undefined && responseJson.actio !== null && responseJson.action.length > 0) {
//if (responseJson.action === "environment_create") {
// navigate("/admin?tab=environments")
//}
}
setLoading(false);
}
})
)
.catch((error) => {
setLoading(false);
console.log(`Error in connecting to ${detectionInfo?.category}: `, error);
toast(`An error occurred while connecting to ${detectionInfo?.category}`);
});
}
const filteredRules = ruleInfo === "default" ? [] : ruleInfo?.filter((rule) =>
rule.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
rule.description.toLowerCase().includes(searchQuery.toLowerCase())
)
return (
<Container>
<Paper
style={{
marginTop: 50,
width: "100%",
padding: 50,
backgroundColor: theme.palette.backgroundColor,
borderRadius: theme.palette.borderRadius,
}}
>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 2,
}}
>
<Typography variant="h6" component="div">
{detectionInfo?.name} ({filteredRules?.length})
</Typography>
<Button
variant="contained"
onClick={handleConnectClick}
disabled={loading} // Disable the button while loading
color={isTenzirActive ? "primary" : "secondary"}
style={{ }}
>
{loading ? <CircularProgress size={24} /> : isTenzirActive ? `Connected to ${detectionInfo?.category}` : `Connect to ${detectionInfo?.category}`}
</Button>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 2,
}}
>
<Box
sx={{
display: "flex",
}}
>
<TextField
label="Search rules"
variant="outlined"
size="small"
sx={{ mr: 2 }}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
{/* <Button
color="primary"
variant="contained"
onClick={() => uploadRef.current.click()}
>
<PublishIcon /> Upload sigma file
</Button>
<input
hidden
type="file"
multiple
ref={uploadRef}
onChange={(event) => {
uploadFiles(event.target.files);
}}
/> */}
</Box>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Typography variant="body2" sx={{ mr: 1 }}>
Global disable/enable
</Typography>
<Switch
checked={!folderDisabled}
onChange={() =>
handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl, isTenzirActive)
}
/>
</Box>
</Box>
<Box
sx={{
height: "500px",
width: "100%",
overflowY: "auto",
p: 1,
}}
>
{filteredRules?.length > 0 ?
filteredRules.map((rule, index) => {
console.log("RULE CARD: ", rule);
return (
<DetectionRuleCard
key={index}
ruleName={rule.file_name}
description={rule.description}
file_id={rule.file_id}
globalUrl={globalUrl}
folderDisabled={folderDisabled}
isTenzirActive={isTenzirActive}
{...rule}
/>
)
})
: null }
</Box>
</Paper>
</Container>
);
};
export default Detection;
+162
View File
@@ -0,0 +1,162 @@
import React, { useState, useEffect } from "react";
import { Container, CircularProgress, Typography } from "@mui/material";
import { toast } from "react-toastify";
import Detection from "../views/Detection";
const DetectionDashBoard = (props) => {
const { globalUrl } = props;
const [ruleInfo, setRuleInfo] = useState(null);
const [, setSelectedRule] = useState(null);
const [, setFileData] = useState("");
const [isTenzirActive, setIsTenzirActive] = useState(false);
const [folderDisabled, setFolderDisabled] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [importAttempts, setImportAttempts] = useState(0);
const maxImportAttempts = 2;
useEffect(() => {
const fetchTimeout = setTimeout(() => {
fetchSigmaInfo();
}, 1000); // Delay by 1 second
return () => clearTimeout(fetchTimeout);
}, [globalUrl]);
useEffect(() => {
if (ruleInfo && ruleInfo.length === 0 && importAttempts < maxImportAttempts) {
importSigmaFromUrl();
}
}, [ruleInfo]);
const openEditBar = (rule) => {
setSelectedRule(rule);
fetchFileContent(rule.file_id);
};
const handleSave = (updatedContent) => {
toast("This will be saved");
};
const fetchFileContent = (file_id) => {
setFileData("");
fetch(`${globalUrl}/api/v1/files/${file_id}/content`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for file :O!");
return "";
}
return response.text();
})
.then((respdata) => {
if (respdata.length === 0) {
toast("Failed getting file. Is it deleted?");
return;
}
setFileData(respdata);
})
.catch((error) => {
toast(error.toString());
});
};
const fetchSigmaInfo = () => {
const url = `${globalUrl}/api/v1/files/detection/sigma_rules`;
setIsLoading(true);
fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson["success"] === false) {
toast("Failed to get sigma rules");
} else {
setRuleInfo(responseJson.sigma_info || []);
setFolderDisabled(responseJson.folder_disabled);
setIsTenzirActive(responseJson.is_tenzir_active);
}
setIsLoading(false);
})
.catch((error) => {
setIsLoading(false);
console.log("Error in getting sigma files: ", error);
toast("An error occurred while fetching sigma rules");
setRuleInfo([]);
});
};
const importSigmaFromUrl = () => {
setIsLoading(true);
setImportAttempts((prevAttempts) => prevAttempts + 1);
const url = "https://github.com/satti-hari-krishna-reddy/shuffle_sigma";
const folder = "sigma";
const parsedData = {
url: url,
path: folder,
field_3: "main",
};
toast(`Getting files from url ${url}. This may take a while if the repository is large. Please wait...`);
fetch(`${globalUrl}/api/v1/files/download_remote_enhanced`, {
method: "POST",
mode: "cors",
headers: {
Accept: "application/json",
},
body: JSON.stringify(parsedData),
credentials: "include",
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.success) {
toast("Successfully loaded files from " + url);
fetchSigmaInfo(); // Fetch again after successful import
} else {
toast(responseJson.reason ? `Failed loading: ${responseJson.reason}` : "Failed loading");
}
setIsLoading(false);
})
.catch((error) => {
toast(error.toString());
setIsLoading(false);
});
};
if (isLoading && (!ruleInfo || ruleInfo.length === 0)) {
return (
<Container style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }}>
<div>
<CircularProgress />
<Typography variant="h6" style={{ marginTop: 20 }}>Downloading rules, please wait...</Typography>
</div>
</Container>
);
}
return (
<Container style={{ display: "flex" }}>
<Detection
globalUrl={globalUrl}
ruleInfo={ruleInfo}
folderDisabled={folderDisabled}
setFolderDisabled={setFolderDisabled}
isTenzirActive={isTenzirActive}
/>
</Container>
);
};
export default DetectionDashBoard;
+102 -27
View File
@@ -1,15 +1,14 @@
import React, { useEffect, useLayoutEffect, useRef, useState } from "react"
import React, { useEffect, useLayoutEffect, useRef, useState, useContext, memo } from "react"
import { toast } from 'react-toastify';
import Markdown from 'react-markdown'
import theme from '../theme.jsx';
import ReactJson from "react-json-view";
import ReactJson from "react-json-view-ssr";
import { isMobile } from "react-device-detect";
import { BrowserView, MobileView } from "react-device-detect";
import { useParams, useNavigate, Link } from "react-router-dom";
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import remarkGfm from 'remark-gfm'
import { Context } from "../context/ContextApi.jsx";
import {
Grid,
TextField,
@@ -26,7 +25,6 @@ import {
ListItemButton,
ListItemText
} from "@mui/material";
import {
Link as LinkIcon,
Edit as EditIcon,
@@ -35,7 +33,6 @@ import {
FileCopy as FileCopyIcon
} from "@mui/icons-material";
import { fontGrid } from "@mui/material/styles/cssUtils.js";
import { active } from "d3";
const Body = {
//maxWidth: 1000,
@@ -46,6 +43,7 @@ const Body = {
height: "100%",
color: "white",
position: "relative",
paddingTop: 40,
//textAlign: "center",
};
@@ -125,7 +123,7 @@ export const Paragrah = (props) => {
return (
<div class="sdf">
<div>
{element}
</div>
)
@@ -155,9 +153,28 @@ export const OuterLink = (props) => {
export const Img = (props) => {
return <img style={{ borderRadius: theme.palette.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }} alt={props.alt} src={props.src} />;
// Find parent container and check width
var height = "auto"
var width = 750
if (props.height !== undefined && props.height !== null) {
height = props.height
}
if (props.width !== undefined && props.width !== null) {
width = props.width
}
return(
<img
style={{border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, width: width, maxWidth: width, margin: "auto", marginTop: 10, marginBottom: 10, }}
alt={props.alt}
src={props.src}
/>
)
}
export const CodeHandler = (props) => {
const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : ""
@@ -197,7 +214,7 @@ export const CodeHandler = (props) => {
backgroundColor: theme.palette.inputColor,
overflowY: "auto",
// Have it inline
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
}}
>
{validate.valid === true ?
@@ -234,10 +251,8 @@ export const CodeHandler = (props) => {
}
const Docs = (defaultprops) => {
const { globalUrl, selectedDoc, serverside, serverMobile } = defaultprops;
const { globalUrl, selectedDoc, serverside, serverMobile, isLoggedIn, isLoaded } = defaultprops;
let navigate = useNavigate();
// Quickfix for react router 5 -> 6
const params = useParams();
//var props = JSON.parse(JSON.stringify(defaultprops))
@@ -369,8 +384,7 @@ const Docs = (defaultprops) => {
hash = hash.split('?')[0]
}
if (hash) {
console.log("HASH: ", hash)
const element = document.getElementById(hash)
const element = document.getElementById(hash.toLowerCase())
if (element) {
element.scrollIntoView({
behavior: "instant",
@@ -454,6 +468,30 @@ const Docs = (defaultprops) => {
minHeight: "80vh",
};
const noteLabelStyle = {
fontWeight: "bold",
color: "#f86a3e",
display: "block",
marginBottom: "5px",
};
const Blockquote = ({ children }) => {
const textContent = children.map(child =>
child.props && child.props.children ? child.props.children.join('') : child
).join('').trim();
// Maybe some more contents....
const isNote = textContent.startsWith("[!TIP]");
return (
<blockquote style={isNote ? alertNote : {}}>
{isNote && <span style={noteLabelStyle}>Tips:</span>}
{isNote ? textContent.replace("[!TIP]", "").trim() : children}
</blockquote>
);
};
const Heading = (props) => {
const [hover, setHover] = useState(false);
var id = props.children[0].toLowerCase().toString()
@@ -487,7 +525,7 @@ const Docs = (defaultprops) => {
style={{
backgroundColor: theme.palette.inputColor,
padding: 15,
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
marginBottom: 30,
display: "flex",
}}
@@ -837,6 +875,12 @@ const Docs = (defaultprops) => {
fontSize: isMobile ? "1.3rem" : "1.1rem",
};
const alertNote = {
padding: "10px",
borderLeft: "5px solid #f86a3e",
backgroundColor: "rgb(26,26,26)",
};
const CustomButton = (props) => {
const { title, icon, link } = props
@@ -847,7 +891,7 @@ const Docs = (defaultprops) => {
target="_blank"
style={{ textDecoration: "none", color: "inherit", flex: 1, margin: 10, }}
>
<div style={{ cursor: hover ? "pointer" : "default", borderRadius: theme.palette.borderRadius, flex: 1, border: "1px solid rgba(255,255,255,0.3)", backgroundColor: hover ? theme.palette.surfaceColor : theme.palette.inputColor, padding: 25, }}
<div style={{ cursor: hover ? "pointer" : "default", borderRadius: theme.palette?.borderRadius, flex: 1, border: "1px solid rgba(255,255,255,0.3)", backgroundColor: hover ? theme.palette.surfaceColor : theme.palette.inputColor, padding: 25, }}
onClick={(event) => {
if (link === "" || link === undefined) {
event.preventDefault()
@@ -887,7 +931,7 @@ const Docs = (defaultprops) => {
return (
<Link to={link} style={hrefStyle}>
<div style={{ width: "100%", height: 80, cursor: hover ? "pointer" : "default", borderRadius: theme.palette.borderRadius, border: "1px solid rgba(255,255,255,0.3)", backgroundColor: hover ? theme.palette.surfaceColor : theme.palette.inputColor, }}
<div style={{ width: "100%", height: 80, cursor: hover ? "pointer" : "default", borderRadius: theme.palette?.borderRadius, border: "1px solid rgba(255,255,255,0.3)", backgroundColor: hover ? theme.palette.surfaceColor : theme.palette.inputColor, }}
onMouseOver={() => {
setHover(true)
}}
@@ -923,8 +967,8 @@ const Docs = (defaultprops) => {
Documentation
</Typography>
<div style={{ display: "flex", marginTop: 25, }}>
<CustomButton title="Talk to Support" icon=<img src="/images/Shuffle_logo_new.png" style={{ height: 35, width: 35, border: "", borderRadius: theme.palette.borderRadius, }} /> />
<CustomButton title="Ask the community" icon=<img src="/images/social/discord.png" style={{ height: 35, width: 35, border: "", borderRadius: theme.palette.borderRadius, }} /> link="https://discord.gg/B2CBzUm" />
<CustomButton title="Talk to Support" icon=<img src="/images/Shuffle_logo_new.png" style={{ height: 35, width: 35, border: "", borderRadius: theme.palette?.borderRadius, }} /> />
<CustomButton title="Ask the community" icon=<img src="/images/social/discord.png" style={{ height: 35, width: 35, border: "", borderRadius: theme.palette?.borderRadius, }} /> link="https://discord.gg/B2CBzUm" />
</div>
<div style={{ textAlign: "left" }}>
@@ -971,9 +1015,11 @@ const Docs = (defaultprops) => {
h6: Heading,
a: OuterLink,
p: Paragrah,
blockquote: Blockquote,
}
// PostDataBrowser Section
const postDataBrowser =
list === undefined || list === null ? null : (
@@ -1022,8 +1068,10 @@ const Docs = (defaultprops) => {
<Markdown
components={markdownComponents}
id="markdown_wrapper"
className={"style.reactMarkdown"}
escapeHtml={false}
skipHtml={false}
remarkPlugins={[remarkGfm]}
style={{
maxWidth: "100%", minWidth: "100%",
}}
@@ -1217,14 +1265,41 @@ const Docs = (defaultprops) => {
);
// Padding and zIndex etc set because of footer in cloud.
const loadedCheck = (
<div style={{ minHeight: 1000, zIndex: 50000, maxWidth: 1920, minWidth: isMobile ? null : 1366, margin: "auto", }}>
<BrowserView>{postDataBrowser}</BrowserView>
<MobileView>{postDataMobile}</MobileView>
</div>
);
const loadedCheck = (
<DocsWrapper isLoggedIn={isLoggedIn} isLoaded={isLoaded}>
<DocsContent postDataBrowser={postDataBrowser} postDataMobile={postDataMobile}/>
</DocsWrapper>
);
return <div>{loadedCheck}</div>;
return <div style={{}}>{loadedCheck}</div>;
};
export default Docs;
const DocsContent = memo(({postDataBrowser, postDataMobile}) => {
return(
<div>
<BrowserView>{postDataBrowser}</BrowserView>
<MobileView>{postDataMobile}</MobileView>
</div>
)})
const DocsWrapper = memo(({isLoggedIn, isLoaded, children })=>{
const { leftSideBarOpenByClick, windowWidth } = useContext(Context);
return (
<div style={{
minHeight: 1000, zIndex: 1,
maxWidth: Math.min(!(isLoggedIn && isLoaded) ? 1920 : leftSideBarOpenByClick ? windowWidth - 300 : windowWidth - 200, 1920),
minWidth: isMobile ? null : (isLoggedIn && isLoaded) ? leftSideBarOpenByClick ? 800 : 900 : null, margin: "auto",
position: (isLoggedIn && isLoaded) && leftSideBarOpenByClick ? "relative" : "static",
left: (isLoggedIn && isLoaded) && leftSideBarOpenByClick ? 120 : (isLoggedIn && isLoaded) && !leftSideBarOpenByClick ? 80 : 0,
marginLeft: windowWidth < 1920 ? leftSideBarOpenByClick && (isLoggedIn && isLoaded) ? 160 : (isLoggedIn && isLoaded) && !leftSideBarOpenByClick ? 80 : 0 : "auto", width: "100%",
transition: "left 0.3s ease-in-out, min-width 0.3s ease-in-out, max-width 0.3s ease-in-out, position 0.3s ease-in-out, margin 0.3s ease-in-out, margin-left 0.3s ease"
}}>
{children}
</div>
)
})
+43
View File
@@ -0,0 +1,43 @@
import React from 'react';
import { Box, Typography, Button, TextField } from '@mui/material';
const EditComponent = ({ ruleName, description, content, setContent, lastEdited, editedBy, onSave }) => {
const handleSave = () => {
onSave(content);
};
return (
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: 2, height: '100%', width: '100%', marginTop:'30px'}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6">{ruleName}</Typography>
</Box>
<Typography variant="body2" style={{ marginTop: '2%' }}>
{description}
</Typography>
<Typography variant="body2" sx={{ mt: 1 }}>
Last edited: {lastEdited}
</Typography>
<Typography variant="body2">
Edited By: {editedBy}
</Typography>
<Box sx={{ mt: 2 }}>
<TextField
multiline
rows={12}
value={content}
onChange={(e) => setContent(e.target.value)}
variant="outlined"
fullWidth
/>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 2 }}>
<Button variant="contained" color="primary" onClick={handleSave}>
Save
</Button>
</Box>
</Box>
);
};
export default EditComponent;
+1 -1
View File
@@ -2467,7 +2467,7 @@ const GettingStarted = (props) => {
maxWidth: 1024,
zIndex: 11,
border: "1px solid rgba(255,255,255,0.1)",
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
textAlign: "center",
overflow: "auto",
}}
+5 -2
View File
@@ -166,7 +166,7 @@ const LoginDialog = (props) => {
} else {
if (responseJson["reason"] === "MFA_REDIRECT") {
setLoginInfo(
"MFA required. Please the 6-digit code from your authenticator"
"MFA required. Please enter the 6-digit code from your authenticator"
);
setMFAField(true);
return;
@@ -183,7 +183,10 @@ const LoginDialog = (props) => {
if (responseJson.tutorials === undefined || responseJson.tutorials === null || !responseJson.tutorials.includes("welcome")) {
console.log("RUN Welcome!!")
window.location.pathname = "/welcome"
setTimeout(() => {
navigate("/welcome?tab=2")
},200)
// window.location.pathname = ""
return
}
File diff suppressed because it is too large Load Diff
+17 -14
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo } from "react";
import React, { useState, useEffect, useMemo, useContext } from "react";
import theme from "../theme.jsx";
import { isMobile } from "react-device-detect";
@@ -7,28 +7,29 @@ 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 { Context } from "../context/ContextApi.jsx";
import {
Apps as AppsIcon,
Code as CodeIcon,
EmojiObjects as EmojiObjectsIcon,
Chat as ChatIcon,
BorderBottom,
PeopleAltOutlined as PeopleAltOutlinedIcon,
DescriptionOutlined as DescriptionOutlinedIcon,
} from "@mui/icons-material";
import PeopleAltOutlinedIcon from '@mui/icons-material/PeopleAltOutlined';
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
import {Typography} from "@mui/material";
// Should be different if logged in :|
const Search = (props) => {
const { globalUrl, isLoaded, serverside, userdata, hidemargins, isHeader } =
props;
let navigate = useNavigate();
const { leftSideBarOpenByClick } = useContext(Context);
const [curTab, setCurTab] = useState(0);
const iconStyle = { marginRight: isHeader ? null : 10 };
@@ -43,11 +44,10 @@ const Search = (props) => {
const params = Object.fromEntries(urlSearchParams.entries());
const foundTab = params["tab"];
if (foundTab !== null && foundTab !== undefined) {
for (var key in Object.keys(views)) {
for (let key in views) {
const value = views[key];
console.log(key, value);
if (value === foundTab) {
setConfig("", key);
setConfig(null, key);
break;
}
}
@@ -91,7 +91,7 @@ const Search = (props) => {
userdata={userdata}
/>, [curTab]);
const MemoizedDiscordChat = useMemo(() => <DiscordChat isMobile={isMobile} />)
const MemoizedDiscordChat = useMemo(() => <DiscordChat isMobile={isMobile} />, [curTab])
const useStyles = makeStyles({
hideIndicator: {
@@ -123,7 +123,6 @@ const Search = (props) => {
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,
@@ -131,6 +130,8 @@ const Search = (props) => {
flexDirection: "column",
overflowX: "hidden",
minHeight: 400,
paddingLeft: leftSideBarOpenByClick ? 270 : 80,
transition: "padding-left 0.3s ease",
};
const views = {
@@ -138,7 +139,9 @@ const Search = (props) => {
1: "workflows",
2: "docs",
3: "creators",
4: "discord"
};
const setConfig = (event, inputValue) => {
const newValue = parseInt(inputValue);
@@ -216,19 +219,19 @@ const Search = (props) => {
<div style={boxStyle}>
<Tabs
style={{
width: 741,
width: 757,
margin: isHeader ? null : "auto",
marginTop: hidemargins === true ? 0 : isHeader ? null : 25,
backgroundColor: "rgba(33, 33, 33, 1)",
borderRadius: 8
borderRadius: 8,
}}
value={curTab}
indicatorColor="primary"
textColor="secondary"
onChange={setConfig}
aria-label="disabled tabs example"
variant="scrollable"
scrollButtons="auto"
// variant="scrollable"
// scrollButtons="auto"
classes={{ indicator: classes.hideIndicator, root: classes.customTab }}
>
<StyledTab
+14 -8
View File
@@ -3,6 +3,8 @@ import React, { useState } from "react";
import { Typography, CircularProgress } from "@mui/material";
import theme from '../theme.jsx';
import { red, } from "../views/AngularWorkflow.jsx"
const SetAuthentication = (props) => {
const { globalUrl } = props;
@@ -300,7 +302,7 @@ const SetAuthentication = (props) => {
}
return (
<div style={{ maringTop: 50, padding: 50, border: "1px solid rgba(255,255,255,0.6)", borderRadius: theme.palette.borderRadius, width: 500, margin: "auto", itemAlign: "center", textAlign: "center",}}>
<div style={{ padding: 50, border: failed === true ? `1px solid ${red}` : "1px solid rgba(255,255,255,0.6)", borderRadius: theme.palette?.borderRadius, width: 500, margin: "auto", paddingTop: 50, itemAlign: "center", textAlign: "center",}}>
<Typography
variant="h4"
style={{ marginLeft: "auto", marginRight: "auto", marginTop: 50}}
@@ -311,18 +313,22 @@ const SetAuthentication = (props) => {
variant="h6"
style={{ marginLeft: "auto", marginRight: "auto", marginTop: 50}}
>
{!finished ? (
{!finished ?
failed ?
null :
<CircularProgress />
) : (
"Done - this window should close within 3 seconds."
)}
<div />
{failed ? "Failed setup. Error: " : ""} {response}
:
failed ?
null
:
"Done - this window should close within 3 seconds."
}
<div style={{marginTop: 10, }} />
<b>{failed ? "Failed auth. Error: " : ""}</b> {response}
<br/>
<br/>
{failed ? "If the error persists, try to use fewer scopes. Contact our support at support@shuffler.io if you need further assistance. You may close this window." : ""}
{failed ? "If the error persists, try to use fewer scopes. Contact support@shuffler.io if you need further assistance, and include the current URL and a screenshot. You may now close this window." : ""}
</Typography>
</div>
);
+91 -7
View File
@@ -10,6 +10,7 @@ import {
Divider,
TextField,
Modal,
Switch,
} from "@mui/material";
//import { useAlert
import { ToastContainer, toast } from "react-toastify";
@@ -19,6 +20,9 @@ import { FileCopy, Visibility, VisibilityOff } from "@mui/icons-material";
import IconButton from "@mui/material/IconButton";
import { Tooltip } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { Box } from "@mui/system";
import CircularProgress from "@mui/material/CircularProgress";
const Settings = (props) => {
const { globalUrl, isLoaded, userdata, setUserData } = props;
@@ -32,6 +36,11 @@ const Settings = (props) => {
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [newPassword2, setNewPassword2] = useState("");
const [selectedOrganization, setSelectedOrganization] = useState({});
const [MFARequired, setMFARequired] = React.useState(false);
const [image2FA, setImage2FA] = React.useState("");
const [value2FA, setValue2FA] = React.useState("");
// const [file, setFile] = React.useState("");
// const [fileBase64, setFileBase64] = React.useState(
// userdata.image === undefined || userdata.image === null
@@ -87,6 +96,82 @@ const Settings = (props) => {
flexDirection: "column",
};
useEffect(() => {
if (userdata.active_org === undefined || userdata.active_org === null || userdata.active_org.id === undefined || userdata.active_org.id === null) {
return;
}
fetch(`${globalUrl}/api/v1/orgs/${userdata.active_org.id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
console.log("Received response:", response);
if (response.status !== 200) {
console.error("Status not 200:", response.status);
throw new Error(`Status not 200: ${response.status}`);
}
return response.json();
})
.then((responseJson) => {
setSelectedOrganization(responseJson);
setMFARequired(responseJson.mfa_required);
})
.catch((error) => {
console.error("Error fetching organization:", error);
});
}, [userdata]);
const UpdateMFAInUserOrg = (org_id) => {
if (MFARequired === false) {
toast("Making MFA required for your organization. Please wait...");
} else {
toast("Making MFA optional for your organization. Please wait...");
}
const data = {
mfa_required: !selectedOrganization.mfa_required,
org_id: selectedOrganization.id,
}
const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`;
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
crossDomain: true,
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) =>
response.json().then((responseJson) => {
console.log(responseJson)
if (responseJson["success"] === false) {
toast.error("Failed updating org: ", responseJson.reason);
} else {
if (MFARequired === false) {
setMFARequired(true)
toast.success("Successfully make MFA required for your organization!");
} else {
setMFARequired(false)
toast.success("Successfully make MFA optional for your organization!")
}
}
}),
)
.catch((error) => {
toast("Err: " + error.toString());
});
}
const checkOwner = (data, userdata) => {
var currentOwner = false;
if (data.owner.address === userdata.eth_info.account) {
@@ -542,7 +627,7 @@ const Settings = (props) => {
display: "flex",
flexDirection: "column",
padding: "0px 0px 12px 0px",
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
};
const currentOwner = checkOwner(data, userdata);
@@ -551,7 +636,7 @@ const Settings = (props) => {
}
return (
<Grid item xs={4} style={{ borderRadius: theme.palette.borderRadius }}>
<Grid item xs={4} style={{ borderRadius: theme.palette?.borderRadius }}>
<Paper style={innerPaperStyle}>
<img
src={data.image}
@@ -607,7 +692,7 @@ const Settings = (props) => {
);
const landingpageData = (
<div style={{ display: "flex", marginTop: 120 }}>
<div style={{ display: "flex", paddingTop: 120 }}>
<Paper style={boxStyle}>
{imageInfo}
<h2>Settings</h2>
@@ -938,8 +1023,7 @@ const Settings = (props) => {
>
Submit password change
</Button>
<h3>{passwordFormMessage}</h3>
<h3>{passwordFormMessage}</h3>
{isCloud && (
<>
@@ -981,7 +1065,7 @@ const Settings = (props) => {
<Paper
square
style={{
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
padding: 50,
backgroundColor: theme.palette.inputColor,
}}
@@ -1016,7 +1100,7 @@ const Settings = (props) => {
<Paper
square
style={{
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
padding: 50,
backgroundColor: theme.palette.inputColor,
}}
+8 -14
View File
@@ -24,7 +24,7 @@ const SetAuthentication = (props) => {
const [loadFail, setLoadFail] = useState("");
const [appAuthentication, setAppAuthentication] = React.useState([]);
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
//const alert = useAlert();
const parseIncomingOpenapiData = (data) => {
@@ -142,8 +142,8 @@ const SetAuthentication = (props) => {
// 3. Help them set info for the app
// Make sure to test both private and public apps
const appname = app.name !== undefined ? app.name : "";
const appLink = "/apps/" + app.id || "";
const appname = app.name !== undefined ? app.name : ""
const appLink = "/apps/" + app.id || ""
console.log("App: ", app)
@@ -154,21 +154,16 @@ const SetAuthentication = (props) => {
:
<><div>
<Typography variant="h4" style={{ marginBottom: 20, }}>
A Shuffle Organization has invited you to: Configure <a href={appLink} target="_blank" style={{ color: '#FF8444', textDecoration: 'none' }}>{appname}</a> Authentication
You are invited to: Configure <a href={appLink} target="_blank" style={{ color: '#FF8444', textDecoration: 'none' }}>{appname}</a> Authentication
</Typography>
{/* What does this mean box */}
<Typography variant="h6" style={{ marginBottom: 20, }}>
What does this mean?
</Typography>
<Typography variant="body1" style={{ marginBottom: 20, }}>
<Typography variant="body1" style={{ marginBottom: 20, color: "rgba(255,255,255,0.4)", }}>
A Shuffle Organization has invited you to configure authentication for this app so that they can use this authentication in one of their workflows.
</Typography>
<Typography variant="h6">
Authenticate Here:
</Typography>
<Typography variant="body1" style={{ marginBottom: 20, }}>
{app.authentication === undefined || app.authentication === null || app.authentication.length === 0 ?
null
@@ -195,7 +190,7 @@ const SetAuthentication = (props) => {
appAuthentication={appAuthentication} />}
</Typography>
<Typography variant="h6" style={{ marginBottom: 20, }}>
<Typography variant="h6" style={{ marginTop: 50, marginBottom: 20, }}>
What can they do with this?
</Typography>
@@ -203,12 +198,11 @@ const SetAuthentication = (props) => {
You can check the actions they want to use <a href={appLink} target="_blank" style={{ color: '#FF8444', textDecoration: 'none' }}>here</a>.
</Typography>
<Typography variant="body1" style={{ marginBottom: 20, }}>
{/* Add a box below */}
<Typography variant="body1" style={{ marginBottom: 20, color: "rgba(255,255,255,0.4)",}}>
<div className="collapsible-container">
<div className="collapsible-list">
{app.actions?.map((item, index) => (
<div key={index} className="collapsible-item">
<div key={index} className="collapsible-item" style={{cursor: "pointer", }}>
<div className="collapsible-label" onClick={() => handleToggle(index)}>
{item.label}
</div>
+29 -23
View File
@@ -21,6 +21,7 @@ import {
Paper,
Chip,
Checkbox,
Fade,
} from "@mui/material";
import {
@@ -62,6 +63,7 @@ import {
TreeMapLabel,
TreeMapRect,
} from 'reaviz';
import { isMobile } from "react-device-detect"
const useStyles = makeStyles({
notchedOutline: {
@@ -381,7 +383,7 @@ const UsecaseListComponent = (props) => {
<Typography variant="h6">
{usecase.name}
</Typography>
<Grid container spacing={3} style={{marginTop: 25}}>
<Grid container spacing={3} style={{marginTop: isMobile ? null:25, padding: isMobile?20:null}}>
{usecase.list.map((subcase, subindex) => {
const selectedItem = subindex === expandedItem && index === expandedIndex
@@ -426,7 +428,7 @@ const UsecaseListComponent = (props) => {
const fixedName = subcase.name.toLowerCase().replace("_", " ")
return (
<Grid id={fixedName} item xs={selectedItem ? 12 : 4} key={subindex} style={{minHeight: 110,}} onClick={() => {
<Grid id={fixedName} item xs={isMobile ? 12 : selectedItem ? 12 : 4} key={subindex} style={{minHeight: 110,}} onClick={() => {
if (fixedName === "reporting") {
getUsecase(subcase, index, subindex)
return
@@ -439,11 +441,11 @@ const UsecaseListComponent = (props) => {
navigate(`/usecases?selected_object=${fixedName}`)
}
}}>
<Paper style={{padding: 25, minHeight: isCloud ? 75 : 122, cursor: !selectedItem ? "pointer" : "default", border: itemBorder, backgroundColor: backgroundColor, borderRadius: theme.palette.borderRadius, }} onClick={() => {
<Paper style={{padding: 25, minHeight: isCloud ? 75 : 122, cursor: !selectedItem ? "pointer" : "default", border: itemBorder, backgroundColor: backgroundColor, borderRadius: theme.palette?.borderRadius, }} onClick={() => {
}}>
{!selectedItem ?
<div style={{textAlign: "left", position: "relative",}}>
<Typography variant="h6" style={{maxWidth: 215}}>
<Typography variant="h6" style={{maxWidth: isMobile? null:215}}>
<b>{subcase.name}</b>
</Typography>
{finished ?
@@ -478,7 +480,7 @@ const UsecaseListComponent = (props) => {
<IconButton
style={{
position: "absolute",
bottom: -25,
bottom: isMobile ? 11 :-20,
right: -15,
}}
onClick={(e) => {
@@ -503,7 +505,7 @@ const UsecaseListComponent = (props) => {
<IconButton
style={{
position: "absolute",
bottom: -65,
bottom: isMobile ? -22 : 10,
right: -15,
}}
onClick={(e) => {
@@ -618,7 +620,7 @@ const UsecaseListComponent = (props) => {
</IconButton>
</Tooltip>
</div>
<div style={{marginTop: 25, display: "flex", minHeight: 400, maxHeight: 400, marginRight: 15, }}>
<div style={{marginTop: isMobile? null:25, padding: isMobile?20:null, width: isMobile?"100%":null, display: isMobile ? null:"flex", minHeight: isMobile ? "auto":400, maxHeight: isMobile ? null:400, marginRight: isMobile?null:15 }}>
{editing ?
<div style={{flex: 1, marginRight: 50, }}>
<TextField
@@ -797,7 +799,7 @@ const UsecaseListComponent = (props) => {
style={{
backgroundColor: theme.palette.inputColor,
height: 50,
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
}}
onChange={(event, newValue) => {
//handleWorkflowSelectionUpdate({ target: { value: newValue} })
@@ -854,7 +856,7 @@ const UsecaseListComponent = (props) => {
<Tooltip arrow placement="left" title={
<span style={{}}>
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
<img src={data.image} alt={newname} style={{backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }} />
<img src={data.image} alt={newname} style={{backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette?.borderRadius, }} />
: null}
<Typography>
Choose {newname}
@@ -879,7 +881,7 @@ const UsecaseListComponent = (props) => {
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
}}
{...params}
label="Find your workflows"
@@ -950,8 +952,8 @@ const UsecaseListComponent = (props) => {
target="_blank"
style={{ textDecoration: "none", color: "rgba(255,255,255,0.7)", marginRight: 5, }}
>
<div style={{width: 160, display: "flex", borderRadius: theme.palette.borderRadius, cursor: "pointer", border: highlight ? "2px solid #f86a3e" : "1px solid rgba(255,255,255,0.7)", backgroundColor: theme.palette.inputColor, padding: "0px 0px 15px 15px", overflow: "hidden",}}>
<img src={subdata.image} style={{width: 40, height: 40, borderRadius: theme.palette.borderRadius, marginTop: 15, }} />
<div style={{width: 160, display: "flex", borderRadius: theme.palette?.borderRadius, cursor: "pointer", border: highlight ? "2px solid #f86a3e" : "1px solid rgba(255,255,255,0.7)", backgroundColor: theme.palette.inputColor, padding: "0px 0px 15px 15px", overflow: "hidden",}}>
<img src={subdata.image} style={{width: 40, height: 40, borderRadius: theme.palette?.borderRadius, marginTop: 15, }} />
<Typography variant="body1" style={{lineHeight: "95%", marginLeft: 12, marginTop: marginTop === 0 ? 19 : 25, maxHeight: 34, }}>
{subdata.name}
</Typography>
@@ -979,8 +981,9 @@ const UsecaseListComponent = (props) => {
}
<div style={{
height: 350,
width: 350,
borderRadius: theme.palette.borderRadius,
width: isMobile? null:350,
marginTop: isMobile ? 50:null,
borderRadius: theme.palette?.borderRadius,
border: "1px solid rgba(255,255,255,0.3)",
padding: 5,
backgroundColor: theme.palette.backgroundColor,
@@ -1066,8 +1069,8 @@ const RadialChart = ({keys, setSelectedCategory}) => {
}}>
<RadialAreaChart
id="workflow_categories"
height={500}
width={500}
height={isMobile ? 400:500}
width={isMobile ? null:500}
data={keys}
axis={<RadialAxis type="category" />}
series={
@@ -1110,7 +1113,7 @@ const RadialChart = ({keys, setSelectedCategory}) => {
}}
content={(data, color) => {
return (
<div style={{borderRadius: theme.palette.borderRadius, backgroundColor: theme.palette.inputColor, border: "1px solid rgba(255,255,255,0.3)", color: "white", padding: 5, cursor: "pointer",}}>
<div style={{borderRadius: theme.palette?.borderRadius, backgroundColor: theme.palette.inputColor, border: "1px solid rgba(255,255,255,0.3)", color: "white", padding: 5, cursor: "pointer",}}>
<Typography variant="body1">
{data.x}
</Typography>
@@ -1675,15 +1678,15 @@ const Dashboard = (props) => {
console.log("USECASES: ", usecases)
const data =
<div className="content" style={{width: 1000, margin: "auto", paddingBottom: 200, textAlign: "center",}}>
<div style={{width: 500, margin: "auto"}}>
<div className="content" style={{width: isMobile ? "100%": 1000, margin: "auto", paddingBottom: 200, textAlign: "center",}}>
<div style={{width: isMobile ? null:500, margin: "auto"}}>
{keys.length > 0 ?
<RadialChart keys={keys} setSelectedCategory={setSelectedUsecaseCategory} />
: null}
</div>
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
<div style={{ display: "flex", marginLeft: 180, }}>
<div style={{ display: isMobile ? null:"flex", marginLeft: isMobile? null :180, }}>
{usecases.map((usecase, index) => {
return (
<Chip
@@ -1693,6 +1696,7 @@ const Dashboard = (props) => {
marginRight: 10,
paddingLeft: 5,
paddingRight: 5,
marginTop: isMobile? 10:null,
height: 28,
cursor: "pointer",
border: `1px solid ${usecase.color}`,
@@ -1738,9 +1742,11 @@ const Dashboard = (props) => {
</div>
const dataWrapper =
<div style={{ maxWidth: 1366, margin: "auto" }}>
{data}
</div>
<Fade in={true} timeout={1250}>
<div style={{ maxWidth: 1366, margin: "auto" }}>
{data}
</div>
</Fade>
return dataWrapper
File diff suppressed because it is too large Load Diff
+13 -9
View File
@@ -32,7 +32,7 @@ const Welcome = (props) => {
const [inputUsecase, setInputUsecase] = useState({});
const [frameworkData, setFrameworkData] = useState(undefined);
const [discoveryWrapper, setDiscoveryWrapper] = useState(undefined);
const [activeStep, setActiveStep] = React.useState(1);
const [activeStep, setActiveStep] = React.useState(0);
const [apps, setApps] = React.useState([]);
const [defaultSearch, setDefaultSearch] = React.useState("")
const [selectionOpen, setSelectionOpen] = React.useState(false)
@@ -47,7 +47,7 @@ const Welcome = (props) => {
}
}, [activeStep])
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
const [steps, setSteps] = useState([
"Help us get to know you",
"Find your Apps",
@@ -285,6 +285,9 @@ const Welcome = (props) => {
}
setActiveStep(foundTab-1)
//setRenderDone(
}
}
}, [])
@@ -301,14 +304,14 @@ const Welcome = (props) => {
minWidth: isMobile ? null : 275,
backgroundColor: theme.palette.surfaceColor,
color: "white",
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
}
const actionObject = {
padding: "25px",
maxHeight: 280,
minHeight: 280,
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
}
const imageStyle = {
@@ -337,7 +340,7 @@ const Welcome = (props) => {
const defaultImage = "/images/experienced.png"
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 : defaultImage
return (
<div style={{ margin: "auto", paddingBottom: 150, minHeight: 1500, marginTop: 50, }}>
<div style={{ margin: "auto", paddingBottom: 150, minHeight: 1500, paddingTop: 50, }}>
{/*
<div style={{position: "fixed", bottom: 110, right: 110, display: "flex", }}>
<img src="/images/Arrow.png" style={{width: 250, height: "100%",}} />
@@ -351,7 +354,7 @@ const Welcome = (props) => {
color="primary"
style={{
backgroundColor: theme.palette.platformColor,
borderRadius: theme.palette.borderRadius,
borderRadius: theme.palette?.borderRadius,
padding: 12,
border: "1px solid rgba(255,255,255,0.3)",
maxWidth: 500,
@@ -398,6 +401,7 @@ const Welcome = (props) => {
discoveryWrapper={discoveryWrapper}
setDiscoveryWrapper={setDiscoveryWrapper}
appFramework={frameworkData}
setAppFramework={setFrameworkData}
getFramework={getFramework}
steps={steps}
skipped={skipped}
@@ -430,7 +434,7 @@ const Welcome = (props) => {
We will use this information to personalize your automation
</Typography> */}
<div style={{display: isMobile ? null : "flex", marginTop: 70, width: isMobile ? 280 : 700, margin: "auto",}}>
<div style={{border: "2px solid #806BFF", borderRadius: theme.palette.borderRadius, }}>
<div style={{border: "2px solid #806BFF", borderRadius: theme.palette?.borderRadius, }}>
<Card style={paperObject} onClick={() => {
if (isCloud) {
ReactGA.event({
@@ -462,7 +466,7 @@ const Welcome = (props) => {
OR
</Typography> */}
</div>
<Card style={paperObject} onClick={() => {
<Card disabled style={paperObject} onClick={() => {
if (isCloud) {
ReactGA.event({
category: "welcome",
@@ -474,7 +478,7 @@ const Welcome = (props) => {
navigate("/workflows?message=Skipped intro")
}}>
<CardActionArea style={actionObject}>
<img src={experienced_image} style={{padding: experienced_image === defaultImage ? 2 : 10, objectFit: "scale-down", minHeight: experienced_image === defaultImage ? 40 : 70, maxHeight: experienced_image === defaultImage ? 40 : 70, bordeRadius: theme.palette.borderRadius*2, marginBottom: experienced_image === defaultImage ? 24 : 2 }} />
<img src={experienced_image} style={{padding: experienced_image === defaultImage ? 2 : 10, objectFit: "scale-down", minHeight: experienced_image === defaultImage ? 40 : 70, maxHeight: experienced_image === defaultImage ? 40 : 70, bordeRadius: theme.palette?.borderRadius*2, marginBottom: experienced_image === defaultImage ? 24 : 2 }} />
<Typography variant="h4" style={{color: "#F1F1F1"}}>
Experienced
</Typography>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff