import React, { memo, useContext, useEffect, useState } from 'react'; import theme from "../theme.jsx"; import { Tooltip, Typography, Switch, TextField, Button, ButtonGroup, List, ListItem, ListItemText, IconButton, Dialog, DialogTitle, DialogActions, DialogContent, Checkbox, Divider, Tab, Tabs, Collapse, Skeleton, Grid, Chip, MenuItem, } from "@mui/material"; import { CopyToClipboard } from "../views/Docs.jsx" import { FileCopy as FileCopyIcon, CheckCircle as CheckCircleIcon, Cached as CachedIcon, Cloud as CloudIcon, Cancel as CancelIcon, Help as HelpIcon, ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon, } from "@mui/icons-material"; import { toast } from 'react-toastify'; import { Context } from '../context/ContextApi.jsx'; import { green, red } from '../views/AngularWorkflow.jsx' import AppSearch from "../components/AppSearch1.jsx"; const EnvironmentTab = memo((props) => { const { globalUrl, isCloud, userdata, selectedOrganization } = props; const [environments, setEnvironments] = React.useState([]); const [showArchived, setShowArchived] = React.useState(false); const [modalUser, setModalUser] = React.useState({}); const [loginInfo, setLoginInfo] = React.useState(""); const [modalOpen, setModalOpen] = React.useState(false); const [showLoader, setShowLoader] = useState(true) const [commandController, setCommandController] = React.useState({ pipelines: false, proxies: false, }) const [installationTab, setInstallationTab] = React.useState(0); const [isExpanded, setIsExpanded] = React.useState(false); const [listItemExpanded, setListItemExpanded] = React.useState(-1); const [, setUpdate] = React.useState(0); const [showDistributionPopup, setShowDistributionPopup] = React.useState(false); const [selectedEnvironment, setSelectedEnvironment] = React.useState(null); const [selectedSubOrg, setSelectedSubOrg] = React.useState([]); const [showLocationActionModal, setShowLocationActionModal] = React.useState(undefined) useEffect(() => { getEnvironments(); setModalUser({}); }, []); const changeModalData = (field, value) => { modalUser[field] = value; }; // Horrible frontend fix for environments const setDefaultEnvironment = (environment) => { // FIXME - add more checks to this toast("Setting default location to " + environment.Name); var newEnv = []; for (var key in environments) { if (environments[key].id == environment.id) { if (environments[key].archived) { toast("Can't set archived to default"); return; } environments[key].default = true; } else if ( environments[key].default == true && environments[key].id !== environment.id ) { environments[key].default = false; } newEnv.push(environments[key]); } // Just use this one? const url = globalUrl + "/api/v1/setenvironments"; fetch(url, { method: "PUT", credentials: "include", body: JSON.stringify(newEnv), headers: { "Content-Type": "application/json", }, }) .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { toast(responseJson.reason); setTimeout(() => { getEnvironments(); }, 1500); } else { setLoginInfo(""); setModalOpen(false); setTimeout(() => { getEnvironments(); }, 1500); } }), ) .catch((error) => { console.log("Error in backend data: ", error); }); }; const rerunCloudWorkflows = (environment) => { toast("Starting execution reruns. This can run in the background."); fetch(`${globalUrl}/api/v1/environments/${environment.id}/rerun`, { method: "GET", credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!"); return; } else { toast(response.reason); //toast("Aborted all dangling workflows"); } return response.json(); }) .then((responseJson) => { console.log("Got response for execution: ", responseJson); //console.log("RESPONSE: ", responseJson) //setFiles(responseJson) }) .catch((error) => { //toast(error.toString()) }); } const getEnvironments = () => { fetch(globalUrl + "/api/v1/getenvironments", { 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!"); return; } return response.json(); }) .then((responseJson) => { setEnvironments(responseJson); setShowLoader(false) // Helper info for users in case they have a large queue and don't know about queue flushing if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) { if (responseJson.length === 1 && responseJson[0].Type !== "cloud") { setListItemExpanded(0) } for (var i = 0; i < responseJson.length; i++) { const env = responseJson[i]; // Check if queuesize is too large if (env.queue !== undefined && env.queue !== null && env.queue > 100) { toast("Queue size for " + env.name + " is very large. We recommend you to reduce it by flushing the queue before continuing."); break } } } }) .catch((error) => { toast(error.toString()); }); }; const flushQueue = (name) => { // Just use this one? const url = globalUrl + "/api/v1/flush_queue"; fetch(url, { method: "DELETE", credentials: "include", headers: { "Content-Type": "application/json", }, }) .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { toast(responseJson.reason); getEnvironments(); } else { setLoginInfo(""); setModalOpen(false); getEnvironments(); } }), ) .catch((error) => { console.log("Error when deleting: ", error); }); }; const deleteEnvironment = (environment) => { // FIXME - add some check here ROFL //const name = environment.name //toast("Modifying environment " + name) //var newEnv = [] //for (var key in environments) { // if (environments[key].Name == name) { // if (environments[key].default) { // toast("Can't modify the default environment") // return // } // if (environments[key].type === "cloud" && !environments[key].archived) { // toast("Can't modify cloud environments") // return // } // environments[key].archived = !environments[key].archived // } // newEnv.push(environments[key]) //} const id = environment.id; //toast("Modifying environment " + environment.Name) var newEnv = []; for (var key in environments) { if (environments[key].id == id) { if (environments[key].default) { toast("Can't modify the default environment. Change the default environment first."); return; } if (environments[key].type === "cloud" && !environments[key].archived) { toast("Can't modify cloud environments"); return; } environments[key].archived = !environments[key].archived; } newEnv.push(environments[key]); } // Just use this one? const url = globalUrl + "/api/v1/setenvironments"; fetch(url, { method: "PUT", credentials: "include", body: JSON.stringify(newEnv), headers: { "Content-Type": "application/json", }, }) .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { toast(responseJson.reason); getEnvironments(); } else { setLoginInfo(""); setModalOpen(false); getEnvironments(); } }), ) .catch((error) => { console.log("Error when deleting: ", error); }); }; const abortEnvironmentWorkflows = (environment) => { //console.log("Aborting all workflows started >10 minutes ago, not finished"); toast( "Clearing the queue - this may take some time. A new will show up when finished.", ); fetch( `${globalUrl}/api/v1/environments/${environment.id}/stop?deleteall=true`, { method: "GET", credentials: "include", }, ) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!"); toast("Failed aborting dangling workflows"); return; } else { toast("Successfully cleared the queue"); getEnvironments(); } return response.json(); }) .then((responseJson) => { console.log("Got response for execution: ", responseJson); //console.log("RESPONSE: ", responseJson) //setFiles(responseJson) }) .catch((error) => { //toast(error.toString()) }); }; const changeRecommendation = (recommendation, action) => { const data = { action: action, name: recommendation.name, }; fetch(`${globalUrl}/api/v1/recommendations/modify`, { mode: "cors", method: "POST", body: JSON.stringify(data), credentials: "include", crossDomain: true, withCredentials: true, headers: { "Content-Type": "application/json; charset=utf-8", }, }) .then((response) => { if (response.status === 200) { } else { } return response.json(); }) .then((responseJson) => { if (responseJson.success === true) { getEnvironments(); } else { if ( responseJson.success === false && responseJson.reason !== undefined ) { toast("Failed change recommendation: ", responseJson.reason); } else { toast("Failed change recommendation"); } } }) .catch((error) => { toast( "Failed dismissing alert. Please contact support@shuffler.io if this persists.", ); }); }; const getOrborusCommand = (environment) => { if (environment.Type === "cloud") { //toast("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.",) return } if ( props.userdata.active_org === undefined || props.userdata.active_org === null ) { return; } const elementName = "copy_element_shuffle"; var auth = environment.auth === "" ? "cb5st3d3Z!3X3zaJ*Pc" : environment.auth // Escape exclamation marks for copying auth = auth.replace("\\!", "!").replace(/!/g, "\\!") const newUrl = globalUrl === "https://shuffler.io" ? "https://shuffle-backend-stbuwivzoq-nw.a.run.app" : globalUrl; var skipPipeline = false if (commandController.pipelines === true) { skipPipeline = true } var addProxy = false if (commandController.proxies === true) { addProxy = true } if (installationTab === 1) { return (`docker run -d \\ --restart=always \\ --name="shuffle-orborus" \\ --pull=always \\ --volume "/var/run/docker.sock:/var/run/docker.sock" \\ -e AUTH="${auth}" \\ -e ENVIRONMENT_NAME="${environment.Name}" \\ -e ORG="${environment.org_id}" \\ -e SHUFFLE_WORKER_IMAGE="ghcr.io/shuffle/shuffle-worker:latest" \\ -e SHUFFLE_SWARM_CONFIG=run \\ -e SHUFFLE_LOGS_DISABLED=true \\ -e BASE_URL="${newUrl}" \\${addProxy ? ` -e HTTPS_PROXY=IP:PORT \\` : ""}${skipPipeline ? ` -e SHUFFLE_SKIP_PIPELINES=true \\` : ""} ghcr.io/shuffle/shuffle-orborus:latest `) } else if (installationTab === 2) { return `https://shuffler.io/docs/configuration#kubernetes` } const commandData = `docker rm shuffle-orborus --force; \\\ndocker run -d \\ --restart=always \\ --name="shuffle-orborus" \\ --pull=always \\ --volume "/var/run/docker.sock:/var/run/docker.sock" \\ -e AUTH="${auth}" \\ -e ENVIRONMENT_NAME="${environment.Name}" \\ -e ORG="${props.userdata.active_org.id}" \\ -e BASE_URL="${newUrl}" \\${addProxy ? ` -e HTTPS_PROXY=IP:PORT \\` : ""}${skipPipeline ? ` -e SHUFFLE_SKIP_PIPELINES=true \\` : ""} ghcr.io/shuffle/shuffle-orborus:latest` return commandData }; const submitEnvironment = (data) => { // FIXME - add some check here ROFL environments.push({ name: data.environment, type: "onprem", }); // Just use this one? var baseurl = globalUrl; const url = baseurl + "/api/v1/setenvironments"; fetch(url, { method: "PUT", credentials: "include", body: JSON.stringify(environments), headers: { "Content-Type": "application/json", }, }) .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { setLoginInfo("Error in input: " + responseJson.reason); getEnvironments(); } else { setLoginInfo(""); setModalOpen(false); getEnvironments(); } }), ) .catch((error) => { console.log("Error in userdata: ", error); }); }; const modalView = ( ); const textColor = "#9E9E9E !important"; const handleSelectSubOrg = (id, action) => { if (action === "all") { const childOrgs = userdata.orgs.filter( (data) => data.creator_org === userdata.active_org.id ); setSelectedSubOrg((prev) => { if (prev.length === childOrgs.length) { // If all child orgs are already selected, clear the selection return []; } else { // Otherwise, select all child org IDs return childOrgs.map((data) => data.id); } }); } else if (action === "none") { setSelectedSubOrg([]); } else { setSelectedSubOrg((prev) => { if (prev.includes(id)) { return prev.filter((data) => data !== id); } else { return [...prev, id]; } }); } }; const queueSizeText = (queue) => { if (queue === undefined || queue === null) return 0; if (queue < 0) return 0; if (queue > 1000) return ">1000"; return queue; }; const LocationActionModal = (props) => { const { showLocationActionModal } = props const [searchQuery, setSearchQuery] = React.useState(""); if (showLocationActionModal === undefined || showLocationActionModal === null) { return null } if (showLocationActionModal?.open !== true) { return null } return ( ) } const editEnvironmentConfig = (id, selectedSubOrg, cacheKey) => { const data = { action: "suborg_distribute", selected_suborgs: selectedSubOrg, } const url = `${globalUrl}/api/v1/environments/${id}/config`; 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 overwriting environments"); } else { toast("Successfully updated environments!"); setTimeout(() => { getEnvironments(); setShowDistributionPopup(false); }, 1000); } }) ) .catch((error) => { toast("Err: " + error.toString()); }); }; const changeDistribution = (id, selectedSubOrg) => { editEnvironmentConfig(id, [...new Set(selectedSubOrg)]) } const EnvironmentDistributionModal = showDistributionPopup ? ( ) : null; return (
{getOrborusCommand(environment)}