Further cloud syncs for MUlti Tenant distribution
This commit is contained in:
@@ -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.
|
||||
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.
|
||||
<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, }}/>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -4122,7 +4122,10 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,15 +299,13 @@ 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);
|
||||
} else {
|
||||
runAlgoliaAppSearch(params.appid);
|
||||
|
||||
//handleEditApp()
|
||||
}
|
||||
if (params.appid.length === 32 || params.appid.length === 36) {
|
||||
handleEditApp(params.appid);
|
||||
runAlgoliaAppSearch(params.appid, false, true);
|
||||
} else {
|
||||
runAlgoliaAppSearch(params.appid);
|
||||
|
||||
//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
|
||||
|
||||
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(appid)
|
||||
|
||||
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,13 +1545,11 @@ 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);
|
||||
} else {
|
||||
runAlgoliaAppSearch(params.appid);
|
||||
}
|
||||
if (params.appid.length === 32 || params.appid.length === 36) {
|
||||
handleEditApp(params.appid);
|
||||
runAlgoliaAppSearch(params.appid, false, true);
|
||||
} else {
|
||||
runAlgoliaAppSearch(params.appid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> 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> 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"
|
||||
|
||||
Reference in New Issue
Block a user