Further cloud syncs for MUlti Tenant distribution

This commit is contained in:
Frikky
2025-02-13 13:28:17 +01:00
parent b367b4cfb7
commit 1f2e0ed6a3
6 changed files with 96 additions and 246 deletions
+20 -6
View File
@@ -734,9 +734,9 @@ const EnvironmentTab = memo((props) => {
<div style={{ height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' }}>
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}>
<div style={{ marginBottom: 20 }}>
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}>Locations</h2>
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}>Runtime Locations</h2>
<span style={{ color: textColor }}>
Decides which Orborus <b>runtime location</b> to run your workflows in. Previously Environments. <br /> If you have scale problems, talk to our team: support@shuffler.io.&nbsp;
Decides which Orborus <b>runtime location</b> to run your workflows in. Previously called Environments. <br /> If you have scale problems, <a href="https://shuffler.io/docs/configuration#high-availability" target="_blank" rel="noopener noreferrer" style={{ color: "#FF8444" }}>check the docs</a> or talk to our team: support@shuffler.io.&nbsp;
<a
target="_blank"
rel="noopener noreferrer"
@@ -1310,7 +1310,7 @@ const EnvironmentTab = memo((props) => {
<div style={{minWidth: 700, maxWidth: 700, minHeight: 350, display: 'flex', justifyContent: "center", backgroundColor: "transparent", }}>
<div style={{ paddingTop: 50, paddingBottom: 100, }}>
<Typography variant="h6">
Your Self-Hosted Orborus instance
Self-Hosted Orborus instance
</Typography>
<Typography variant="body2" color="textSecondary">
Orborus is the Shuffle queue handler that runs your hybrid workflows and manages pipelines. It can be run in Docker/k8s container on your server or in your cluster. Follow the steps below, and configure as need be.
@@ -1399,9 +1399,23 @@ const EnvironmentTab = memo((props) => {
>
{getOrborusCommand(environment)}
</code>
<CopyToClipboard
text={orborusCommandWrapper()}
/>
<div
style={{
position: "absolute",
right: 0,
top: -10,
}}
>
<IconButton
onClick={() => {
navigator.clipboard.writeText(orborusCommandWrapper())
toast("Copied to clipboard")
}}
>
<FileCopyIcon />
</IconButton>
</div>
</div>
<Divider style={{marginTop: 25, marginBottom: 10, }}/>
+36 -31
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState, useContext, useCallback, useMemo } from "react";
import React, { useEffect, useRef, useState, useContext, useCallback, useMemo, memo } from "react";
import {
ExpandLess as ExpandLessIcon,
ExpandMore as ExpandMoreIcon,
@@ -754,35 +754,6 @@ useEffect(() => {
fontSize: 18
};
const modalView = (
<Dialog
open={searchBarModalOpen}
onClose={() => {
setSearchBarModalOpen(false);
}}
PaperProps={{
style: {
color: "white",
minWidth: 750,
height: 785,
borderRadius: 16,
border: "1px solid var(--Container-Stroke, #494949)",
background: "var(--Container, #000000)",
boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)",
zIndex: 13000,
paddingTop: 20,
},
}}
>
<DialogContent className='dialog-content' style={{}}>
<SearchBox globalUrl={globalUrl} serverside={serverside} userdata={userdata} />
</DialogContent>
<Divider style={{overflow: "hidden"}}/>
<span style={{display:"flex", width:"100%", height:30}}>
</span>
</Dialog>
);
const getRegionFlag = (region_url) => {
var region = "gb";
const regionMapping = {
@@ -821,7 +792,7 @@ useEffect(() => {
height: "calc((100vh - 32px)*1.2)",
}}
>
{modalView}
{searchBarModalOpen ? <ModalView serverside={serverside} userdata={userdata} searchBarModalOpen={searchBarModalOpen} setSearchBarModalOpen={setSearchBarModalOpen} globalUrl={globalUrl} /> : null}
<Box
sx={{
display: "flex",
@@ -1908,3 +1879,37 @@ useEffect(() => {
};
export default LeftSideBar;
const ModalView = memo(({searchBarModalOpen, setSearchBarModalOpen, globalUrl, serverside, userdata}) => {
return (
(
<Dialog
open={searchBarModalOpen}
onClose={() => {
setSearchBarModalOpen(false);
}}
PaperProps={{
style: {
color: "white",
minWidth: 750,
height: 785,
borderRadius: 16,
border: "1px solid var(--Container-Stroke, #494949)",
background: "var(--Container, #000000)",
boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)",
zIndex: 13000,
paddingTop: 20,
},
}}
>
<DialogContent className='dialog-content' style={{}}>
<SearchBox globalUrl={globalUrl} serverside={serverside} userdata={userdata} />
</DialogContent>
<Divider style={{overflow: "hidden"}}/>
<span style={{display:"flex", width:"100%", height:30}}>
</span>
</Dialog>
)
)
});
@@ -310,11 +310,13 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
if(region === "US") {
region = "us-west2"
} else if(region === "EU") {
region = "europe-west3"
region = "europe-west2"
} else if(region === "CA") {
region = "northamerica-northeast1"
} else if(region === "UK") {
region = "europe-west2"
} else if (region === "EU-2") {
region = "europe-west3"
}
var data = {
+1 -1
View File
@@ -3,7 +3,7 @@ import { Link, useNavigate, useLocation } from "react-router-dom";
import Billing from "../components/Billing.jsx";
import Priorities from "../components/Priorities.jsx";
import Branding from "../components/Branding.jsx";
// import AnalyticsTab from '../components/AnalyticsTab.jsx';
import AnalyticsTab from '../components/AnalyticsTab.jsx';
import EditOrgTab from '../components/EditOrgTab.jsx';
import CloudSyncTab from '../components/CloudSyncTab.jsx';
import SSOTab from "../components/ssoTab.jsx"
+3
View File
@@ -4122,6 +4122,9 @@ const AngularWorkflow = (defaultprops) => {
setCookie("session_token", sessionToken, { path: "/" });
window.location.href = "https://shuffler.io/workflows/3abdfb21-b40f-4e50-b855-ac0d62f83cbe";
}, 2000)
} else if (execFound !== null && response.status >= 300) {
toast.info("Failed to load the workflow, but you may still find a list of workflow runs if you have access.")
setExecutionModalOpen(true)
}
}
}
+20 -194
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useContext, useRef } from "react";
import React, { useState, useEffect, useContext } from "react";
import theme from "../theme.jsx";
import ReactGA from "react-ga4";
@@ -223,7 +223,6 @@ const AppExplorer = (props) => {
result: baseResult,
});
const [executing, setExecuting] = useState(false);
const [activatedApps, setActivatedApps] = useState([]);
const [anchorEl, setAnchorEl] = React.useState(null);
const [creatorProfile, setCreatorProfile] = React.useState({});
const [selectedTab, setSelectedTab] = React.useState(0);
@@ -248,7 +247,6 @@ const AppExplorer = (props) => {
})
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
const isFirstRequestSentRef = useRef(false);
// FIXME: This is used, as useEffect() creates an issue with apps not loading at all
@@ -301,7 +299,6 @@ const AppExplorer = (props) => {
if (serverside) {
console.log("Not getting app because serverside.");
} else {
if (isCloud) {
if (params.appid.length === 32 || params.appid.length === 36) {
handleEditApp(params.appid);
runAlgoliaAppSearch(params.appid, false, true);
@@ -311,7 +308,6 @@ const AppExplorer = (props) => {
//handleEditApp()
}
}
}
//parseIncomingOpenapiData(YAML.parse(data))
}
@@ -348,86 +344,6 @@ const AppExplorer = (props) => {
loadOrganization(userdata.active_org.id)
}
const getApps = () => {
// Get apps from localstorage
var storageApps = []
try {
const appstorage = localStorage.getItem("apps")
storageApps = JSON.parse(appstorage)
if (storageApps === null || storageApps === undefined || storageApps.length === 0) {
storageApps = []
} else {
setActivatedApps(storageApps)
}
} catch (e) {
//console.log("Failed to get apps from localstorage: ", e)
}
fetch(globalUrl + "/api/v1/apps", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!");
//if (isCloud) {
// window.location.pathname = "/search";
//}
}
return response.json();
})
.then((responseJson) => {
var privateapps = [];
var valid = [];
var invalid = [];
for (var key in responseJson) {
const app = responseJson[key];
if (app.categories !== undefined && app.categories !== null && app?.categories.includes("Eradication")) {
app.categories = ["EDR"]
}
if (app.is_valid && !(!app.activated && app.generated)) {
privateapps.push(app);
} else if (
app.private_id !== undefined &&
app.private_id.length > 0
) {
valid.push(app);
} else {
invalid.push(app);
}
}
privateapps.push(...valid);
privateapps.push(...invalid);
setActivatedApps(privateapps);
if (params.appid.length === 32 || params.appid.length === 36) {
handleEditApp(params.appid, privateapps);
runAlgoliaAppSearch(params.appid, false, true, privateapps);
} else {
runAlgoliaAppSearch(params.appid,true, false, privateapps);
}
})
.catch((error) => {
toast(error.toString());
});
};
useEffect(() => {
if (activatedApps?.length === 0 && !isCloud) {
getApps();
}
}, [activatedApps]);
var activateButton = (
<Link to={`/apps/new?id=${appId}`} style={{ textDecoration: "none" }}>
<Button
@@ -869,101 +785,14 @@ const AppExplorer = (props) => {
});
};
const HandleActivateApp = (appid) => {
if (isFirstRequestSentRef.current) {
console.log("Skipping duplicate activation request for app:", appid);
return;
}
isFirstRequestSentRef.current = true;
toast.info("App is not activated. Activating it now. Please wait...");
const baseURL = globalUrl;
const url = `${baseURL}/api/v1/apps/${appid}/activate`;
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => response.json())
.then((responseJson) => {
if (!responseJson.success) {
toast.error("Failed to activate app. Redirecting to https://shuffler.io. Please wait...");
setTimeout(() => {
window.location.replace(`https://shuffler.io/apps/${appid}`);
}, 2000);
return;
}
localStorage.removeItem("apps");
getApps();
})
.catch((error) => {
console.log("App activation error: ", error.toString());
toast.error("Failed to activate app. Redirecting to https://shuffler.io. Please wait...");
setTimeout(() => {
window.location.href = `https://shuffler.io/apps/${appid}`;
}, 2000);
});
};
const handleEditApp = (appid, privateapps) => {
const handleEditApp = (appid) => {
if (serverside === true) {
return;
}
var newAppId = appid
var appIdFound = false
setAppId(appid)
if (!isCloud) {
// Ensure privateapps is always an array
privateapps = Array.isArray(privateapps) ? privateapps : [];
let found = privateapps.find(app => {
return app.id?.trim().toLowerCase() === appid
});
if (!found) {
found = privateapps.find(app => {
return app.published_id?.trim().toLowerCase() === appid;
});
}
if (found && found.id) {
newAppId = found.id;
appIdFound = true
} else {
if (!isFirstRequestSentRef.current) {
HandleActivateApp(appid);
return;
} else {
toast.info("App activated but can't load it for now. Loading app on https://shuffler.io. Please wait...");
setTimeout(() => {
window.location.href = `https://shuffler.io/apps/${appid}`;
}, 3000);
}
}
}
setAppId(newAppId)
if (isFirstRequestSentRef.current && !isCloud && !appIdFound) {
return
}
fetch(`${globalUrl}/api/v1/apps/${newAppId}/config`, {
fetch(globalUrl + "/api/v1/apps/" + appid + "/config", {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -997,9 +826,9 @@ const AppExplorer = (props) => {
) {
toast("Failed to get the app")
setIsAppLoaded(true)
// setTimeout(() => {
// navigate("/search")
// }, 1000);
setTimeout(() => {
navigate("/search")
}, 1000);
return;
} else {
parseIncomingOpenapiData(responseJson);
@@ -1010,7 +839,6 @@ const AppExplorer = (props) => {
});
};
const parseIncomingAppdata = (data, openapiExists) => {
document.title = data.name + " App - OpenAPI and API";
@@ -1594,7 +1422,7 @@ const AppExplorer = (props) => {
});
};
const runAlgoliaAppSearch = (appname, isOriginal, triggerOnly, privatedapps) => {
const runAlgoliaAppSearch = (appname, isOriginal, triggerOnly) => {
const index = searchClient.initIndex("appsearch");
console.log("Running appsearch for: ", appname);
@@ -1609,7 +1437,7 @@ const AppExplorer = (props) => {
if (hits !== undefined && hits !== null && hits.length === 1) {
found = true
if (isOriginal !== false) {
handleEditApp(hits[0].objectID, privatedapps)
handleEditApp(hits[0].objectID)
} else {
setSecondaryApp(hits[0])
}
@@ -1644,7 +1472,7 @@ const AppExplorer = (props) => {
} else {
if (isOriginal !== false) {
found = true
handleEditApp(hit.objectID, privatedapps);
handleEditApp(hit.objectID);
} else {
console.log("Found second app: ", hit);
hit.name = hit.name.charAt(0).toUpperCase() + hit.name.slice(1);
@@ -1659,7 +1487,7 @@ const AppExplorer = (props) => {
if (!found) {
if (hits.length > 0) {
if (isOriginal !== false) {
handleEditApp(hits[0].objectID, privatedapps)
handleEditApp(hits[0].objectID)
} else {
setSecondaryApp(hits[0]);
}
@@ -1717,7 +1545,6 @@ const AppExplorer = (props) => {
setTriggers([])
//handleEditApp(params.appid)
if (isCloud) {
if (params.appid.length === 32 || params.appid.length === 36) {
handleEditApp(params.appid);
runAlgoliaAppSearch(params.appid, false, true);
@@ -1725,7 +1552,6 @@ const AppExplorer = (props) => {
runAlgoliaAppSearch(params.appid);
}
}
}
const loopQueries =
urlPathQueries === undefined ||
@@ -3216,7 +3042,9 @@ const AppExplorer = (props) => {
const getDownloadUrl = () => {
return `curl -X POST ${globalUrl}/api/v1/download_docker_image \\\n -H \"Authorization: Bearer ${userdata?.apikey}" \\\n -d '{"name": "frikky/shuffle:${app?.name.toLowerCase().replaceAll(' ', '_')}_${app.app_version}"}' \\\n -o image.zip; \\\n docker load -i image.zip`
const appEnding = app?.public === true ? app?.app_verison : app?.id
return `curl -L \ \\\n "${globalUrl}/api/v1/download_docker_image?image=frikky/shuffle:${app?.name.toLowerCase().replaceAll(' ', '_')}_${appEnding}" \\\n -H \"Authorization: Bearer ${userdata?.apikey}" \\\n -o image.zip; \\\n docker load -i image.zip`
}
const renderedActionOptions = deduplicateByName((
@@ -3406,9 +3234,11 @@ const AppExplorer = (props) => {
Use the App onprem
</Typography>
<Typography variant="body2" color="textSecondary" style={{marginTop: 5, }}>
Due to using docker containers with private containers, we had to use a custom registry. Use the command below to download the image to the server if it fails to run.
Due to using docker containers with privately uploaded containers, we had to use a custom registry. Use the command below to download the image to the server if it fails to run.
<b>&nbsp;PS: This does NOT work for ARM containers.</b>
It will authenticate and authorize you, before redirecting to a Signed URL on https://storage.googleapis.com
<b>&nbsp;Now also works for ARM containers!</b>
</Typography>
<div
@@ -4076,10 +3906,6 @@ const AppExplorer = (props) => {
</Dialog>
) : null;
console.log("App: ", app);
console.log("userdata is: ", userdata);
const landingpageDataBrowser = (
<div
style={{
@@ -4218,7 +4044,7 @@ const AppExplorer = (props) => {
{isMobile ? null : (
<Button
variant={(userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appId) || !isCloud) ? "outlined": "contained"}
variant={userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appId) ? "outlined": "contained"}
component="label"
color="primary"
onClick={() => {
@@ -4263,7 +4089,7 @@ const AppExplorer = (props) => {
}}
style={{ height: 40, marginTop: 5 }}
>
{((userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appId) )|| !isCloud ) ?
{userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appId) ?
"Deactivate App"
:
"Activate App"