Merge branch '2.0.0'
This commit is contained in:
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
+5164
-3290
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+142
-111
@@ -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
@@ -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
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
@@ -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>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
@@ -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",
|
||||
}}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+1217
-434
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
+1738
-1511
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user