import React, { useState, useEffect } from "react"; import theme from "../theme.jsx"; import { makeStyles } from "@mui/styles"; import { useNavigate, Link } from "react-router-dom"; import countries from "../components/Countries.jsx"; import CodeEditor from "../components/ShuffleCodeEditor.jsx"; import getLocalCodeData from "../components/ShuffleCodeEditor.jsx"; import CacheView from "../components/CacheView.jsx"; import { FormControl, InputLabel, Paper, OutlinedInput, Checkbox, Card, Chip, Tooltip, FormControlLabel, Typography, Switch, Select, MenuItem, Divider, TextField, Button, ButtonGroup, Tabs, Tab, Grid, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction, IconButton, Avatar, Zoom, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress, Box, InputAdornment, Autocomplete } from "@mui/material"; import { Add as AddIcon, Clear as ClearIcon, Storage as StorageIcon, Edit as EditIcon, FileCopy as FileCopyIcon, SelectAll as SelectAllIcon, OpenInNew as OpenInNewIcon, CloudDownload as CloudDownloadIcon, Description as DescriptionIcon, Polyline as PolylineIcon, CheckCircle as CheckCircleIcon, Close as CloseIcon, Apps as AppsIcon, Image as ImageIcon, Delete as DeleteIcon, Cached as CachedIcon, AccessibilityNew as AccessibilityNewIcon, Lock as LockIcon, Schedule as ScheduleIcon, Cloud as CloudIcon, Business as BusinessIcon, Visibility as VisibilityIcon, VisibilityOff as VisibilityOffIcon, FmdGood as FmdGoodIcon, } from "@mui/icons-material"; //import { useAlert import { ToastContainer, toast } from "react-toastify" import Dropzone from "../components/Dropzone.jsx"; import HandlePaymentNew from "../views/HandlePaymentNew.jsx"; import OrgHeader from "../components/OrgHeader.jsx"; import OrgHeaderexpanded from "../components/OrgHeaderexpanded.jsx"; import Billing from "../components/Billing.jsx"; import Priorities from "../components/Priorities.jsx"; import Branding from "../components/Branding.jsx"; import Files from "../components/Files.jsx"; import { display, style } from "@mui/system"; //import EnvironmentStats from "../components/EnvironmentStats.jsx"; const useStyles = makeStyles({ notchedOutline: { borderColor: "#f85a3e !important", }, }); const ITEM_HEIGHT = 48; const ITEM_PADDING_TOP = 8; const MenuProps = { PaperProps: { style: { maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, width: 500, }, }, getContentAnchorEl: () => null, }; const FileCategoryInput = (props) => { const isSet = props.isSet; console.log("inside filecategoryinput"); console.log("isset value" , isSet); if (isSet){ return ( )} } const Admin = (props) => { const { globalUrl, userdata, serverside, checkLogin, notifications, setNotifications, } = props; var to_be_copied = ""; const classes = useStyles(); let navigate = useNavigate(); const [firstRequest, setFirstRequest] = React.useState(true); const [orgRequest, setOrgRequest] = React.useState(true); const [modalUser, setModalUser] = React.useState({}); const [orgName, setOrgName] = React.useState(""); const [modalOpen, setModalOpen] = React.useState(false); const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false); const [cloudSyncApikey, setCloudSyncApikey] = React.useState(""); const [loading, setLoading] = React.useState(false); const [selectedOrganization, setSelectedOrganization] = React.useState({}); //console.log("Selected: ", selectedOrganization) const [organizationFeatures, setOrganizationFeatures] = React.useState({}); const [loginInfo, setLoginInfo] = React.useState(""); const [curTab, setCurTab] = React.useState(0); const [users, setUsers] = React.useState([]); const [organizations, setOrganizations] = React.useState([]); const [orgSyncResponse, setOrgSyncResponse] = React.useState(""); const [userSettings, setUserSettings] = React.useState({}); const [matchingOrganizations, setMatchingOrganizations] = React.useState([]); const [environments, setEnvironments] = React.useState([]); const [authentication, setAuthentication] = React.useState([]); const [schedules, setSchedules] = React.useState([]); const [selectedUser, setSelectedUser] = React.useState({}); const [newUsername, setNewUsername] = React.useState(""); const [newPassword, setNewPassword] = React.useState(""); const [selectedUserModalOpen, setSelectedUserModalOpen] = React.useState(false); const [selectedAuthentication, setSelectedAuthentication] = React.useState({}); const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false); const [authenticationFields, setAuthenticationFields] = React.useState([]); const [showArchived, setShowArchived] = React.useState(false); const [isDropzone, setIsDropzone] = React.useState(false); const [image2FA, setImage2FA] = React.useState(""); const [value2FA, setValue2FA] = React.useState(""); const [secret2FA, setSecret2FA] = React.useState(""); const [show2faSetup, setShow2faSetup] = useState(false); const [adminTab, setAdminTab] = React.useState(2); const [showApiKey, setShowApiKey] = useState(false); const [billingInfo, setBillingInfo] = React.useState({}); const [selectedStatus, setSelectedStatus] = React.useState([]); useEffect(() => { getUsers() }, []); useEffect(() => { if (isDropzone) { //redirectOpenApi(); setIsDropzone(false); } }, [isDropzone]); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const get2faCode = (userId) => { fetch(`${globalUrl}/api/v1/users/${userId}/get2fa`, { 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 response.json(); }) .then((responseJson) => { //console.log("RESPONSE: ", responseJson) if (responseJson.success === true) { //toast(responseJson.reason) setImage2FA(responseJson.reason); setSecret2FA(responseJson.extra); } }) .catch((error) => { toast(error.toString()); }); }; const getApps = () => { 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!"); } return response.json(); }) .then((responseJson) => { console.log("apps: ", responseJson); //setApps(responseJson) //setFilteredApps(responseJson) //if (responseJson.length > 0) { // setSelectedApp(responseJson[0]) // if (responseJson[0].actions !== null && responseJson[0].actions.length > 0) { // setSelectedAction(responseJson[0].actions[0]) // } else { // setSelectedAction({}) // } //} }) .catch((error) => { toast(error.toString()); }); }; const categories = [ { name: "Ticketing", apps: ["TheHive", "Service-Now", "SecureWorks"], categories: ["tickets", "ticket", "ticketing"], }, ]; /* "SIEM", "Active Directory", "Firewalls", "Proxies web", "SIEM", "SOAR", "Mail", "EDR", "AV", "MDM/MAM", "DNS", "Ticketing platform", "TIP", "Communication", "DDOS protection", "VMS", ] */ //const alert = useAlert(); const handleStatusChange = (event) => { const { value } = event.target; setSelectedStatus(value); handleEditOrg( "", "", selectedOrganization.id, "", {}, {}, value.length === 0 ? ["none"] : value, ) } // Basically just a simple way to get a generated email // This also may help understand how to communicate with users // both inside and outside Shuffle // This could also be generated on the backend const mailsendingButton = (org) => { if (org === undefined || org === null) { return "" } if (users.length === 0) { return "" } // 1 mail based on users that have only apps // Another based on those doing workflows // Another based on those trying usecases(?) or templates // // Start based on edr, siem & ticketing // Talk about enrichment? // Check suggested usecases // Check suggested workflows var your_apps = "- Connecting " var subject_add = 0 var subject = "POC to automate " if (org.security_framework !== undefined && org.security_framework !== null) { if (org.security_framework.cases.name !== undefined && org.security_framework.cases.name !== null && org.security_framework.cases.name !== "") { your_apps += org.security_framework.cases.name.replace("_", " ", -1).replace(" API", "", -1) + ", " if (subject_add < 2) { if (subject_add === 1) { subject += " and " } subject_add += 1 subject += org.security_framework.cases.name.replace("_", " ", -1).replace(" API", "", -1) } } if (org.security_framework.siem.name !== undefined && org.security_framework.siem.name !== null && org.security_framework.siem.name !== "") { your_apps += org.security_framework.siem.name.replace("_", " ", -1).replace(" API", "", -1) + ", " if (subject_add < 2) { if (subject_add === 1) { subject += " and " } subject_add += 1 subject += org.security_framework.siem.name.replace("_", " ", -1).replace(" API", "", -1) } } if (org.security_framework.communication.name !== undefined && org.security_framework.communication.name !== null && org.security_framework.communication.name !== "") { your_apps += org.security_framework.communication.name.replace("_", " ", -1).replace(" API", "", -1) + ", " if (subject_add < 2) { if (subject_add === 1) { subject += " and " } subject_add += 1 subject += org.security_framework.communication.name.replace("_", " ", -1).replace(" API", "", -1) } } if (org.security_framework.edr.name !== undefined && org.security_framework.edr.name !== null && org.security_framework.edr.name !== "") { your_apps += org.security_framework.edr.name.replace("_", " ", -1).replace(" API", "", -1) + ", " if (subject_add < 2) { if (subject_add === 1) { subject += " and " } subject_add += 1 subject += org.security_framework.edr.name.replace("_", " ", -1).replace(" API", "", -1) } } if (org.security_framework.intel.name !== undefined && org.security_framework.intel.name !== null && org.security_framework.intel.name !== "") { your_apps += org.security_framework.intel.name.replace("_", " ", -1).replace(" API", "", -1) + ", " if (subject_add < 2) { if (subject_add === 1) { subject += " and " } subject_add += 1 subject += org.security_framework.intel.name.replace("_", " ", -1).replace(" API", "", -1) } } // Remove comma //subject += "?" your_apps = your_apps.substring(0, your_apps.length - 2) } // Add usecases they may not have tried (from recommendations): org.priorities where item type is usecase var usecases = "- Building usecases like " const active_usecase = org.priorities.filter((item) => item.type === "usecase" && item.active === true) if (active_usecase.length > 0) { for (var i = 0; i < active_usecase.length; i++) { if (active_usecase[i].name.includes("Suggested Usecase: ")) { usecases += active_usecase[i].name.replace("Suggested Usecase: ", "", -1) + ", " } else { usecases += active_usecase[i].name + ", " } } usecases = usecases.substring(0, usecases.length - 2) } if (your_apps.length <= 15) { your_apps = "" } if (usecases.length <= 30) { usecases = "" } var workflow_amount = "a few" var admins = "" // Loop users var lastLogin = 0 for (var i = 0; i < users.length; i++) { if (users[i].username.includes("shuffler")) { continue } if (users[i].role === "admin") { admins += users[i].username + "," } const data = users[i] for (var i = 0; i < data.login_info.length; i++) { if (data.login_info[i].timestamp > lastLogin) { lastLogin = data.login_info[i].timestamp } } } // Remove last comma admins = admins.substring(0, admins.length - 1) if (your_apps.length > 5) { your_apps += "%0D%0A" } if (usecases.length > 5) { usecases += "%0D%0A" } // Get drift username from userdata.username before @ in email const username = userdata.username.substring(0, userdata.username.indexOf("@")) // Check if timestamp is more than 2 weeks ago and add "a while back" to the message const timeComparison = 1209600 const extra_timestamp_text = lastLogin === 0 ? 0 : (Date.now()/1000 - lastLogin) > timeComparison ? " a while back" : "" console.log("LAST LOGIN: " + lastLogin, extra_timestamp_text) // Check if cloud sync is active, and if so, add a message about it const cloudSyncInfo = selectedOrganization.cloud_sync === true ? "- Scale your onprem installation" : "" var body = `Hey,%0D%0A%0D%0AI noticed you tried to use Shuffle${extra_timestamp_text}, and thought you may be interested in a POC. It looks like you have ${workflow_amount} workflows made, but it still doesn't look like you are getting what you wanted out of Shuffle. If you're interested, I'd love to set up a quick call to see if we can help you get more out of Shuffle. %0D%0A%0D%0A Some of the things we can help with:%0D%0A ${your_apps} - Configuring and authenticating your apps%0D%0A ${usecases} - Multi-Tenancy and creating special usecases%0D%0A ${cloudSyncInfo}%0D%0A If you're interested, please let me know a time that works for you, or set up a call here: https://drift.me/${username}` return `mailto:${admins}?bcc=frikky@shuffler.io,binu@shuffler.io&subject=${subject}&body=${body}` } const changeDistribution = (data) => { //changeDistributed(data, !isDistributed) console.log("Should change distribution to be shared among suborgs") editAuthenticationConfig(data.id, "suborg_distribute") } const deleteAuthentication = (data) => { toast("Deleting auth " + data.label); // Just use this one? const url = globalUrl + "/api/v1/apps/authentication/" + data.id; console.log("URL: ", url); fetch(url, { method: "DELETE", credentials: "include", headers: { "Content-Type": "application/json", }, }) .then((response) => response.json().then((responseJson) => { if (responseJson.success === false) { toast("Failed deleting auth"); } else { // Need to wait because query in ES is too fast setTimeout(() => { getAppAuthentication(); }, 1000); //toast("Successfully deleted authentication!") } }) ) .catch((error) => { console.log("Error in userdata: ", error); }); }; const deleteSchedule = (data) => { // FIXME - add some check here ROFL console.log("INPUT: ", data); // Just use this one? const url = globalUrl + "/api/v1/workflows/" + data["workflow_id"] + "/schedule/" + data.id; console.log("URL: ", url); fetch(url, { method: "DELETE", credentials: "include", headers: { "Content-Type": "application/json", }, }) .then((response) => response.json().then((responseJson) => { console.log("RESP: ", responseJson); if (responseJson["success"] === false) { toast("Failed stopping schedule"); } else { setTimeout(() => { getSchedules(); }, 1500); //toast("Successfully stopped schedule!") } }) ) .catch((error) => { console.log("Error in userdata: ", error); }); }; if (userdata.support === true && selectedOrganization.id !== "" && selectedOrganization.id !== undefined && selectedOrganization.id !== null && selectedOrganization.id !== userdata.active_org.id) { toast("Refreshing window to fix org support access") window.location.reload() return null } const handleVerify2FA = (userId, code) => { const data = { code: code, user_id: userId, }; fetch(`${globalUrl}/api/v1/users/${userId}/set2fa`, { 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 { //toast("Wrong code sent.") //toast("Wrong code sent. Please try again.") } return response.json(); }) .then((responseJson) => { if (responseJson.success === true) { toast("Successfully enabled 2fa"); setTimeout(() => { getUsers(); setImage2FA(""); setValue2FA(""); setSecret2FA(""); setShow2faSetup(false); setSelectedUserModalOpen(false); }, 1000); } else { toast("Wrong code sent. Please try again."); //toast("Failed setting 2fa: ", responseJson.reason) } }) .catch((error) => { toast("Wrong code sent. Please try again."); //toast("Err: " + error.toString()) }); }; const handleStopOrgSync = (org_id) => { if (org_id === undefined || org_id === null) { toast("Couldn't get org " + org_id); return; } const data = {}; const url = globalUrl + "/api/v1/orgs/" + org_id + "/stop_sync"; 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) => { if (response.status === 200) { console.log("Cloud sync success?"); toast("Successfully stopped cloud sync"); } else { console.log("Cloud sync fail?"); toast( "Failed stopping sync. Try again, and contact support if this persists." ); } return response.json(); }) .then((responseJson) => { setTimeout(() => { handleGetOrg(org_id); }, 1000); }) .catch((error) => { toast("Err: " + error.toString()); }); }; const enableCloudSync = (apikey, organization, disableSync) => { setOrgSyncResponse(""); const data = { apikey: apikey, organization: organization, disable: disableSync, }; const url = globalUrl + "/api/v1/cloud/setup"; 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) => { setLoading(false); if (response.status === 200) { console.log("Cloud sync success?"); } else { console.log("Cloud sync fail?"); } return response.json(); //setTimeout(() => { //}, 1000) }) .then((responseJson) => { console.log("RESP: ", responseJson); if ( responseJson.success === false && responseJson.reason !== undefined ) { setOrgSyncResponse(responseJson.reason); toast("Failed to handle sync: " + responseJson.reason); } else if (!responseJson.success) { toast("Failed to handle sync."); } else { getOrgs(); if (disableSync) { toast("Successfully disabled sync!"); setOrgSyncResponse("Successfully disabled syncronization"); } else { toast("Cloud Syncronization successfully set up!"); setOrgSyncResponse( "Successfully started syncronization. Cloud features you now have access to can be seen below." ); } selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync; setSelectedOrganization(selectedOrganization); setCloudSyncApikey(""); handleGetOrg(userdata.active_org.id); } }) .catch((error) => { setLoading(false); toast("Err: " + error.toString()); }); }; const saveAuthentication = (authentication) => { const data = authentication; const url = globalUrl + "/api/v1/apps/authentication"; fetch(url, { mode: "cors", method: "PUT", 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 changing authentication"); } else { //toast("Successfully password!") setSelectedUserModalOpen(false); getAppAuthentication(); } }) ) .catch((error) => { toast("Err: " + error.toString()); }); }; const handleEditOrg = ( name, description, orgId, image, defaults, sso_config, lead_info, ) => { const data = { name: name, description: description, org_id: orgId, image: image, defaults: defaults, sso_config: sso_config, lead_info: lead_info, }; 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 editAuthenticationConfig = (id, parentAction) => { const data = { id: id, action: parentAction !== undefined && parentAction !== null ? parentAction : "assign_everywhere", }; const url = globalUrl + "/api/v1/apps/authentication/" + 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 appauth"); } else { toast("Successfully updated auth!"); setSelectedUserModalOpen(false); setTimeout(() => { getAppAuthentication(); }, 1000); } }) ) .catch((error) => { toast("Err: " + error.toString()); }); }; const createSubOrg = (currentOrgId, name) => { const data = { name: name, org_id: currentOrgId }; console.log(data); const url = globalUrl + `/api/v1/orgs/${currentOrgId}/create_sub_org`; 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) { if (responseJson.reason !== undefined) { toast(responseJson.reason); } else { toast("Failed creating suborg. Please try again"); } } else { toast( "Successfully created suborg. Reloading in 3 seconds!" ); setSelectedUserModalOpen(false); setTimeout(() => { window.location.reload(); }, 2500); } setOrgName(""); setModalOpen(false); }) ) .catch((error) => { toast("Err: " + error.toString()); }); }; const onPasswordChange = () => { const data = { username: selectedUser.username, newpassword: newPassword }; const url = globalUrl + "/api/v1/users/passwordchange"; 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) { if (responseJson.reason !== undefined) { toast(responseJson.reason); } else { toast("Failed setting new password"); } } else { toast("Successfully updated password!"); setSelectedUserModalOpen(false); } }) ) .catch((error) => { toast("Err: " + error.toString()); }); }; const deleteUser = (data) => { // Just use this one? const userId = data.id; const url = globalUrl + "/api/v1/users/" + userId; fetch(url, { method: "DELETE", credentials: "include", headers: { "Content-Type": "application/json", }, }) .then((response) => { if (response.status === 200) { getUsers(); } return response.json(); }) .then((responseJson) => { if (!responseJson.success && responseJson.reason !== undefined) { toast("Failed to deactivate user: " + responseJson.reason); } else { toast("Changed activation for user " + data.id); } }) .catch((error) => { console.log("Error in userdata: ", error); }); }; 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; } } if (orgId.length === 0) { 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."); } 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 inviteUser = (data) => { //console.log("INPUT: ", data); setLoginInfo(""); // Just use this one? var data = { username: data.Username, type: "invite", org_id: selectedOrganization.id, }; var baseurl = globalUrl; const url = baseurl + "/api/v1/users/register_org"; fetch(url, { method: "POST", credentials: "include", body: JSON.stringify(data), headers: { "Content-Type": "application/json", }, }) .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { setLoginInfo("Error: " + responseJson.reason); toast("Failed to send email (2). Please try again and contact support if this persists.") } else { setLoginInfo(""); setModalOpen(false); setTimeout(() => { getUsers(); }, 1000); toast("Invite sent! They will show up in the list when they have accepted the invite.") } }) ) .catch((error) => { console.log("Error in userdata: ", error); toast("Failed to send email. Please try again and contact support if this persists.") }); }; const submitUser = (data) => { console.log("INPUT: ", data); setLoginInfo(""); // Just use this one? var data = { username: data.Username, password: data.Password }; var baseurl = globalUrl; const url = baseurl + "/api/v1/users/register"; fetch(url, { method: "POST", credentials: "include", body: JSON.stringify(data), headers: { "Content-Type": "application/json", }, }) .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { setLoginInfo("Error: " + responseJson.reason); } else { setLoginInfo(""); setModalOpen(false); setTimeout(() => { getUsers(); }, 1000); } }) ) .catch((error) => { console.log("Error in userdata: ", error); }); }; // Horrible frontend fix for environments const setDefaultEnvironment = (environment) => { // FIXME - add more checks to this toast("Setting default env 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 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 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 abortEnvironmentWorkflows = (environment) => { //console.log("Aborting all workflows started >10 minutes ago, not 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("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 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"); 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 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); }); }; var localData = ""; const getSchedules = () => { fetch(globalUrl + "/api/v1/workflows/schedules", { 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) => { setSchedules(responseJson); }) .catch((error) => { toast(error.toString()); }); }; const getAppAuthentication = () => { fetch(globalUrl + "/api/v1/apps/authentication", { 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) => { if (responseJson.success === true) { //console.log(responseJson.data) //console.log(responseJson) setAuthentication(responseJson.data); } else { toast("Failed getting authentications"); } }) .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); }) .catch((error) => { toast(error.toString()); }); }; const getOrgs = () => { // API no longer in use, as it's in handleInfo request return; fetch(globalUrl + "/api/v1/orgs", { 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) => { setOrganizations(responseJson); }) .catch((error) => { toast(error.toString()); }); }; const getUsers = () => { fetch(globalUrl + "/api/v1/getusers", { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { // Ahh, this happens because they're not admin // window.location.pathname = "/workflows" return; } return response.json(); }) .then((responseJson) => { setUsers(responseJson); }) .catch((error) => { toast(error.toString()); }); }; const getSettings = () => { fetch(globalUrl + "/api/v1/getsettings", { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 when getting settings :O!"); } return response.json(); }) .then((responseJson) => { setUserSettings(responseJson); }) .catch((error) => { console.log(error); }); }; const views = { 0: "organization", 1: "users", 2: "app_auth", 3: "files", 4: "cache", 5: "schedules", 6: "environments", 7: "suborgs", }; const admin_views = { 0: "organization", 1: "cloud_sync", 2: "priorities", 3: "billing", 4: "branding", }; const setConfig = (event, inputValue) => { const newValue = parseInt(inputValue); setCurTab(newValue); if (newValue === 1) { document.title = "Shuffle - admin - users"; getUsers(); } else if (newValue === 2) { document.title = "Shuffle - admin - app authentication"; getAppAuthentication(); } else if (newValue === 3) { document.title = "Shuffle - admin - Files"; } else if (newValue === 4) { document.title = "Shuffle - admin - Datastore"; //listOrgCache("3fd181b9-fb29-41b7-b2f5-15292265d420"); } else if (newValue === 5) { document.title = "Shuffle - admin - schedules"; getSchedules(); } else if (newValue === 6) { document.title = "Shuffle - admin - environments"; getEnvironments(); } else if (newValue === 7) { document.title = "Shuffle - admin - orgs"; getOrgs(); } else { document.title = "Shuffle - admin"; } if (newValue === 8) { console.log("Should get apps for categories."); } navigate(`/admin?tab=${views[newValue]}`); setModalUser({}); } if (firstRequest) { setFirstRequest(false); document.title = "Shuffle - admin"; getEnvironments(); if (!isCloud) { getUsers(); } else { getSettings(); } 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 adminTab = params["admin_tab"]; if (adminTab !== null && adminTab !== undefined) { for (var key in Object.keys(admin_views)) { const value = admin_views[key]; if (value === adminTab) { setAdminTab(parseInt(key)); setConfig("", 0); break; } } } else { const foundTab = params["tab"]; if (foundTab !== null && foundTab !== undefined) { for (var key in Object.keys(views)) { const value = views[key]; if (value === foundTab) { setConfig("", key); break; } } } } } } if ( selectedOrganization.id === undefined && userdata !== undefined && userdata.active_org !== undefined && orgRequest ) { setOrgRequest(false); handleGetOrg(userdata.active_org.id); } const paperStyle = { maxWidth: 1250, margin: "auto", color: "white", backgroundColor: theme.palette.surfaceColor, marginBottom: 10, padding: 20, }; const changeModalData = (field, value) => { modalUser[field] = value; }; const setUser = (userId, field, value) => { const data = { user_id: userId }; data[field] = value; fetch(globalUrl + "/api/v1/users/updateuser", { method: "PUT", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(data), credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for WORKFLOW EXECUTION :O!"); } else { getUsers(); } return response.json(); }) .then((responseJson) => { if (!responseJson.success && responseJson.reason !== undefined) { toast("Failed setting user: " + responseJson.reason); } else { //toast("Set the user field " + field + " to " + value); toast("Successfully updated user field " + field) if (field !== "suborgs") { setSelectedUserModalOpen(false); } } }) .catch((error) => { console.log(error); }); }; const generateApikey = (user) => { const userId = user.id; const data = { user_id: userId }; toast("Generating new API key") var fetchdata = { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", } if (userId === userdata.id) { fetchdata.method = "GET" } else { fetchdata.body = JSON.stringify(data) } fetch(globalUrl + "/api/v1/generateapikey", fetchdata) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for WORKFLOW EXECUTION :O!"); } else { getUsers(); } return response.json(); }) .then((responseJson) => { console.log("RESP: ", responseJson); if (!responseJson.success && responseJson.reason !== undefined) { toast("Failed getting new: " + responseJson.reason); } else { toast("Got new API key"); } }) .catch((error) => { console.log(error); }); }; const editAuthenticationModal = selectedAuthenticationModalOpen ? ( { setSelectedAuthenticationModalOpen(false); }} PaperProps={{ style: { backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: "800px", minHeight: "320px", }, }} > Edit authentication for {selectedAuthentication.app.name} ( {selectedAuthentication.label}) {selectedAuthentication.fields.map((data, index) => { //console.log("DATA: ", data, selectedAuthentication) return (
{data.key} { authenticationFields[index].value = e.target.value; setAuthenticationFields(authenticationFields); }} />
); })}
) : null; const handleOrgEditChange = (event) => { if (userdata.id === selectedUser.id) { toast("Can't remove orgs from yourself"); return; } console.log("event: ", event.target.value); setMatchingOrganizations(event.target.value); // Workaround for empty orgs if (event.target.value.length === 0) { event.target.value.push("REMOVE"); } setUser(selectedUser.id, "suborgs", event.target.value); //setUser(selectedUser.id, "suborgs", matchingOrganizations) }; const userOrgEdit = selectedUser.id !== undefined && selectedUser.orgs !== undefined && selectedUser.orgs !== null && selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0 ? ( Accessible Sub-Organizations ( {selectedUser.orgs ? selectedUser.orgs.length - 1 : 0}) ) : null; const editUserModal = ( { setSelectedUserModalOpen(false); setImage2FA(""); setValue2FA(""); setSecret2FA(""); setShow2faSetup(false); }} PaperProps={{ style: { backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: "800px", minHeight: "320px", }, }} > Editing {selectedUser.username} {isCloud ? null : (
{ setNewUsername(e.target.value); }} />
)} {isCloud ? null : (
setNewPassword(e.target.value)} />
)} {userOrgEdit}
{show2faSetup ? (
{/**/} {secret2FA !== undefined && secret2FA !== null && secret2FA.length > 0 ? ( Scan the image below with the two-factor authentication app on your phone. If you can’t use a QR code, use the code{" "} {secret2FA} instead. ) : null} {image2FA !== undefined && image2FA !== null && image2FA.length > 0 ? ( {"2 ) : ( )} After scanning the QR code image, the app will display a code that you can enter below.
{ if (event.target.value.length > 6) { return; } setValue2FA(event.target.value); }} />
) : null}
); const GridItem = (props) => { const [expanded, setExpanded] = React.useState(false); const primary = props.data.primary; const secondary = props.data.secondary; const primaryIcon = props.data.icon; const secondaryIcon = props.data.active ? ( ) : ( ) return ( { setExpanded(!expanded); }} > {primaryIcon} {secondaryIcon} {expanded ? (
Usage:  {props.data.limit === 0 ? ( "Unlimited" ) : ( {props.data.usage} / {props.data.limit === "" ? "Unlimited" : props.data.limit} )} {/* Data sharing: {props.data.data_collection} */} Description: {secondary}
) : null}
); }; const itemColor = "black"; var syncList = [ { primary: "Workflows", secondary: "", active: true, icon: , }, { primary: "Apps", secondary: "", active: true, icon: , }, { primary: "Organization", secondary: "", active: true, icon: , }, ]; const cloudSyncModal = ( { setCloudSyncModalOpen(false); }} PaperProps={{ style: { backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: "800px", minHeight: "320px", }, }} > Enable cloud features What does{" "} cloud sync {" "} do?
{ setCloudSyncApikey(event.target.value); }} />
{orgSyncResponse.length > 0 ? ( Error: {orgSyncResponse} ) : null} {syncList.map((data, index) => { return ; })} * New triggers (userinput, hotmail realtime)
* Execute in the cloud rather than onprem
* Apps can be built in the cloud
* Easily share apps and workflows
* Access to powerful cloud search
); const organizationView = curTab === 0 && selectedOrganization.id !== undefined ? (

Organization overview

On this page you can configure individual parts of your organization.{" "} Learn more
{selectedOrganization.id === undefined ? (
Loading Organization
) : (
{/* { console.log("Should go to icon") }} > */} {userdata.support === true ? {/**/} Status : null} { const elementName = "copy_element_shuffle"; const org_id = selectedOrganization.id; var copyText = document.getElementById(elementName); if (copyText !== null && copyText !== undefined) { const clipboard = navigator.clipboard; if (clipboard === undefined) { toast("Can only copy over HTTPS (port 3443)"); return; } navigator.clipboard.writeText(org_id); copyText.select(); copyText.setSelectionRange( 0, 99999 ); /* For mobile devices */ /* Copy the text inside the text field */ document.execCommand("copy"); toast(org_id + " copied to clipboard"); } }} > {selectedOrganization.defaults !== undefined && selectedOrganization.defaults.documentation_reference !== undefined && selectedOrganization.defaults.documentation_reference !== null && selectedOrganization.defaults.documentation_reference.includes("http") ? : null} {selectedOrganization.name.length > 0 ? ( ) : (
Loading Organization
)} { const newValue = parseInt(inputValue); setAdminTab(newValue); //const setConfig = (event, inputValue) => { navigate(`/admin?admin_tab=${admin_views[newValue]}`); }} aria-label="disabled tabs example" > Edit Details /> Cloud Synchronization /> Priorities /> Licensing /> Branding (Beta) /> {adminTab === 0 ? ( ) : adminTab === 1 ? (
Cloud syncronization What does{" "} cloud sync {" "} do? Cloud syncronization is a way of getting more out of Shuffle. Shuffle will ALWAYS make every option open source, but features relying on other users can't be done without a collaborative approach. {isCloud ? (
Currently syncronizing:{" "} {selectedOrganization.cloud_sync_active === true ? "True" : "False"} {selectedOrganization.cloud_sync_active ? ( Syncronization interval:{" "} {selectedOrganization.sync_config.interval === 0 ? "60" : selectedOrganization.sync_config.interval} ) : null} Your Apikey
{ setShowApiKey(!showApiKey) }} > {showApiKey ? : } ) }} required fullWidth={true} disabled={true} autoComplete="cloud apikey" id="apikey_field" margin="normal" placeholder="Cloud Apikey" variant="outlined" defaultValue={userSettings.apikey} type={!isCloud || showApiKey ? "text" : "password"} /> {selectedOrganization.cloud_sync_active ? ( ) : null}
) : (
{ setCloudSyncApikey(event.target.value); }} />
{orgSyncResponse.length > 0 ? ( Message from Shuffle Cloud: {orgSyncResponse} ) : null}
)} Cloud sync features If not otherwise specified, Usage will reset monthly {selectedOrganization.sync_features === undefined || selectedOrganization.sync_features === null ? null : Object.keys(selectedOrganization.sync_features).map(function ( key, index ) { // unnecessary parts if (key === "schedule" || key === "apps" || key === "updates") { return null; } const item = selectedOrganization.sync_features[key]; if (item === null) { return null } const newkey = key.replaceAll("_", " "); const griditem = { primary: newkey, secondary: item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description, limit: item.limit, usage: item.usage === undefined || item.usage === null ? 0 : item.usage, data_collection: "None", active: item.active, icon: , }; return ( ); })}
) : adminTab === 2 ? : adminTab === 3 ? : adminTab === 4 ? : null }
)}
) : null; const modalView = ( { setModalOpen(false); }} PaperProps={{ style: { backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: "800px", minHeight: "320px", }, }} > {curTab === 1 ? "Add user" : curTab === 7 ? "Add Sub-Organization" : "Add environment"} {curTab === 1 && isCloud ? ( We will send an email to invite them to your organization. ) : curTab === 7 ? ( The organization created will become a child of your current organization, and be available to you. ) : null} {curTab === 1 ? (
Username changeModalData("Username", event.target.value) } /> {isCloud ? null : ( Password changeModalData("Password", event.target.value) } /> )}
) : curTab === 7 ? (
Name { setOrgName(event.target.value); }} />
) : curTab === 6 ? (
Environment Name changeModalData("environment", event.target.value) } />
) : null} {loginInfo}
); const usersView = curTab === 1 ? (

User Management

Add, edit, block or change passwords.{" "} Learn more
{selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0 ? ( ) : null} {users === undefined || users === null ? null : users.map((data, index) => { var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } const timeNow = new Date().getTime(); // Get the highest timestamp in data.login_info var lastLogin = "N/A" if (data.login_info !== undefined && data.login_info !== null) { var loginInfo = 0 for (var i = 0; i < data.login_info.length; i++) { if (data.login_info[i].timestamp > loginInfo) { loginInfo = data.login_info[i].timestamp } } if (loginInfo > 0) { lastLogin = new Date(loginInfo * 1000).toISOString().slice(0, 10) + " (" + data.login_info.length + ")" } } return ( { const elementName = "copy_element_shuffle"; var copyText = document.getElementById(elementName); if ( copyText !== null && copyText !== undefined ) { const clipboard = navigator.clipboard; if (clipboard === undefined) { toast( "Can only copy over HTTPS (port 3443)" ); return; } navigator.clipboard.writeText(data.apikey); copyText.select(); copyText.setSelectionRange( 0, 99999 ); /* For mobile devices */ /* Copy the text inside the text field */ document.execCommand("copy"); toast("Apikey copied to clipboard"); } }} > ) } /> { console.log("VALUE: ", e.target.value); setUser(data.id, "role", e.target.value); }} style={{ backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px", }} > Org Admin Org User Org Reader } style={{ minWidth: 135, maxWidth: 135, marginRight: 15 }} /> {selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0 ? ( ) : null} { setSelectedUserModalOpen(true); setSelectedUser(data); // Find matching orgs between current org and current user's access to those orgs if ( userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 && selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0 ) { var active = []; for (var key in userdata.orgs) { const found = selectedOrganization.child_orgs.find( (item) => item.id === userdata.orgs[key].id ); if (found !== null && found !== undefined) { if ( data.orgs === undefined || data.orgs === null ) { continue; } const subfound = data.orgs.find( (item) => item === found.id ); if ( subfound !== null && subfound !== undefined ) { active.push(subfound); } } } setMatchingOrganizations(active); } }} > {/**/} ); })}
) : null; const run2FASetup = (data) => { if (!show2faSetup) { get2faCode(data.id); } else { // Should remove? setImage2FA(""); setSecret2FA(""); } setShow2faSetup(!show2faSetup); //setShow2faSetup(true); }; const filesView = curTab !== 3 ? null : const schedulesView = curTab === 5 ? (

Schedules

Schedules used in Workflows. Makes locating and control easier.{" "} Learn more
{schedules === undefined || schedules === null ? null : schedules.map((schedule, index) => { var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } return ( 0 ? schedule.frequency : {schedule.seconds} seconds } /> {schedule.workflow_id} } /> ); })}
) : null; const appCategoryView = curTab === 8 ? (

Categories

Categories are the categories supported by Shuffle, which are mapped to apps and workflows
{categories.map((data, index) => { if (data.apps.length === 0) { return null; } return ( ); })}
) : null; const updateAppAuthentication = (field) => { setSelectedAuthenticationModalOpen(true); setSelectedAuthentication(field); //{selectedAuthentication.fields.map((data, index) => { var newfields = []; for (var key in field.fields) { newfields.push({ key: field.fields[key].key, value: "", }); } setAuthenticationFields(newfields); }; const authenticationView = curTab === 2 ? (

App Authentication

Control the authentication options for individual apps.   Learn more about App Authentication
{/**/} {/* */} {authentication === undefined || authentication === null ? null : authentication.map((data, index) => { var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } //console.log("Auth data: ", data) if (data.type === "oauth2") { data.fields = [ { key: "url", value: "Secret. Replaced during app execution!", }, { key: "client_id", value: "Secret. Replaced during app execution!", }, { key: "client_secret", value: "Secret. Replaced during app execution!", }, { key: "scope", value: "Secret. Replaced during app execution!", }, ]; } const isDistributed = data.suborg_distributed === true ? true : false; return ( style={{ minWidth: 75, maxWidth: 75 }} /> {/* */} {/* */} { return data.key; }) .join(", ") } style={{ minWidth: 125, maxWidth: 125, overflow: "hidden", }} /> { updateAppAuthentication(data); }} disabled={data.org_id !== selectedOrganization.id ? true : false} > {data.defined ? ( { editAuthenticationConfig(data.id); }} > ) : ( {}} disabled={data.org_id !== selectedOrganization.id ? true : false} > )} { deleteAuthentication(data); }} > {selectedOrganization.id !== undefined && data.org_id !== selectedOrganization.id ? : { changeDistribution(data, !isDistributed) }} /> } ); })}
) : null; 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) { if (checkLogin !== undefined) { checkLogin() 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 environmentView = curTab === 6 ? (

Environments

Decides what Orborus environment to execute an action in a workflow in.{" "} Learn more
{ setShowArchived(!showArchived); }} />{" "} Show disabled {environments === undefined || environments === null ? null : environments.map((environment, index) => { if (!showArchived && environment.archived) { return null; } if (environment.archived === undefined) { return null; } var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } // Check if there's a notification for it in userdata.priorities var showCPUAlert = false var foundIndex = -1 if (userdata !== undefined && userdata !== null && userdata.priorities !== undefined && userdata.priorities !== null && userdata.priorities.length > 0) { foundIndex = userdata.priorities.findIndex(prio => prio.name.includes("CPU") && prio.active === true) if (foundIndex >= 0 && userdata.priorities[foundIndex].name.endsWith(environment.Name)) { showCPUAlert = true } } //console.log("Show CPU alert: ", showCPUAlert) const queueSize = environment.queue !== undefined && environment.queue !== null ? environment.queue < 0 ? 0 : environment.queue > 99 ? ">99" : environment.queue : 0 return ( Not running
: environment.running_ip.split(":")[0] : "N/A" } style={{ minWidth: 200, maxWidth: 200, overflow: "hidden", }} /> { 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) { toast("No active organization yet. Are you logged in?") return } const elementName = "copy_element_shuffle"; const auth = environment.auth === "" ? 'cb5st3d3Z!3X3zaJ*Pc' : environment.auth const newUrl = globalUrl === "https://shuffler.io" ? "https://shuffle-backend-stbuwivzoq-nw.a.run.app" : globalUrl const commandData = `docker run --volume "/var/run/docker.sock:/var/run/docker.sock" -e ENVIRONMENT_NAME="${environment.Name}" -e 'AUTH=${auth}' -e ORG="${props.userdata.active_org.id}" -e DOCKER_API_VERSION=1.40 -e BASE_URL="${newUrl}" --name="shuffle-orborus" -d ghcr.io/shuffle/shuffle-orborus:latest` var copyText = document.getElementById(elementName); if (copyText !== null && copyText !== undefined) { const clipboard = navigator.clipboard; if (clipboard === undefined) { toast("Can only copy over HTTPS (port 3443)"); return; } navigator.clipboard.writeText(commandData); copyText.select(); copyText.setSelectionRange( 0, 99999 ); /* For mobile devices */ /* Copy the text inside the text field */ document.execCommand("copy"); toast("Orborus command copied to clipboard"); } }} > } /> {environment.default ? null : ( )}
{showCPUAlert === false ? null :
90% CPU the server(s) hosting the Shuffle App Runner (Orborus) was found. Need help with High Availability and Scale? Read documentation and Get in touch.
} ); })} {/**/}
) : null; const organizationsTab = curTab === 7 ? (

Organizations

Global admin: control organizations
{userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ? ( {userdata.orgs.map((data, index) => { const isSelected = props.userdata.active_org.id === undefined ? "False" : props.userdata.active_org.id === data.id ? "True" : "False"; const imagesize = 40; const imageStyle = { width: imagesize, height: imagesize, pointerEvents: "none", }; const image = data.image === "" ? ( {data.name} ) : ( {data.name} ); var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } return ( { setCloudSyncModalOpen(true); setSelectedOrganization(data); console.log("INVERT CLOUD SYNC"); }} /> style={{ minWidth: 150, maxWidth: 150 }} /> ); })} ) : null}
) : null; const hybridTab = curTab === 8 ? (

Hybrid

{ console.log("INVERT"); }} /> style={{ minWidth: 150, maxWidth: 150 }} />
) : null; const cacheOrgView = curTab === 4 ? (
) : null; // primary={environment.Registered ? "true" : "false"} const iconStyle = { marginRight: 10 }; const data = (
Organization /> Users /> App Auth /> Files /> Datastore /> Schedules /> Environments /> Tenants /> {/*window.location.protocol == "http:" && window.location.port === "3000" ? Hybrid/> : null*/} {/*window.location.protocol === "http:" && window.location.port === "3000" ? Categories/> : null*/}
{organizationView} {authenticationView} {usersView} {environmentView} {schedulesView} {filesView} {hybridTab} {organizationsTab} {appCategoryView} {cacheOrgView}
); return (
{modalView} {cloudSyncModal} {editUserModal} {editAuthenticationModal} {data}
); }; export default Admin;