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 CacheView from "../components/CacheView.jsx"; import { CopyToClipboard } from "../views/Docs.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, Modal } 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, Cancel as CancelIcon, Dns as DnsIcon, Help as HelpIcon, Flag as FlagIcon, FmdGood as FmdGoodIcon, Warning as WarningIcon, ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon, } 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"; 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 [logsViewModal, setLogsViewModal] = React.useState(false); const [userLogViewing, setUserLogViewing] = React.useState({}); const [ipSelected, setIpSelected] = React.useState(""); const [logsLoading, setLogsLoading] = React.useState(true); const [logs, setLogs] = React.useState([]); 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 [appAuthenticationGroupModalOpen, setAppAuthenticationGroupModalOpen] = React.useState(false); const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]); const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState(""); const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState(""); const [appAuthenticationGroupEnvironment, setAppAuthenticationGroupEnvironment] = React.useState(""); const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState(""); const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]); const [organizationFeatures, setOrganizationFeatures] = React.useState({}); const [loginInfo, setLoginInfo] = React.useState(""); const [curTab, setCurTab] = React.useState(0); const [users, setUsers] = React.useState([]); const [subOrgs, setSubOrgs] = useState([]); const [parentOrg, setParentOrg] = React.useState(null); 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(3); const [showApiKey, setShowApiKey] = useState(false); const [billingInfo, setBillingInfo] = React.useState({}); const [selectedStatus, setSelectedStatus] = React.useState([]); const [webHooks, setWebHooks] = React.useState([]); const [allSchedules, setAllSchedules] = React.useState([]); const [pipelines, setPipelines] = React.useState([]); const [, forceUpdate] = React.useState(); const [MFARequired, setMFARequired] = React.useState(selectedOrganization.mfa_required === undefined ? false : selectedOrganization.mfa_required); const [listItemExpanded, setListItemExpanded] = React.useState(-1); const [installationTab, setInstallationTab] = React.useState(0); const [commandController, setCommandController] = React.useState({ pipelines: false, proxies: false, }) const [, setUpdate] = React.useState(0); useEffect(() => { if (selectedOrganization.mfa_required !== undefined) { setMFARequired(selectedOrganization.mfa_required); } }, [selectedOrganization.mfa_required]); const [showDeleteAccountTextbox, setShowDeleteAccountTextbox] = React.useState(false); const [deleteAccountText, setDeleteAccountText] = React.useState(""); const [regionChangeModalOpen, setRegionChangeModalOpen] = React.useState(false); useEffect(() => { getUsers(); setTimeout(() => { const urlSearchParams = new URLSearchParams(window.location.search); const params = Object.fromEntries(urlSearchParams.entries()); const foundOrgID = params["org_id"] if (foundOrgID !== null && foundOrgID !== undefined) { handleClickChangeOrg(foundOrgID) } const foundTab = params["admin_tab"] if (foundTab !== null && foundTab !== undefined) { if (adminTab === 3) { window.scroll({ top: 450, left: 0, behavior: "smooth", }); } } }, 1500); }, []); useEffect(() => { const urlSearchParams = new URLSearchParams(window.location.search); const params = Object.fromEntries(urlSearchParams.entries()); const foundTab = params["admin_tab"] if (foundTab !== null && foundTab !== undefined) { window.scroll({ top: 450, left: 0, behavior: "smooth", }) } }, [adminTab]); useEffect(() => { if (isDropzone) { //redirectOpenApi(); setIsDropzone(false); } }, [isDropzone]); useEffect(() => { if ( userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ) { handleGetSubOrgs(userdata.active_org.id); } else console.log("error in user data"); }, [userdata]); useEffect(() => { handleGetAllTriggers() }, []); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const setSelectedRegion = (region) => { // send a POST request to /api/v1/orgs/{org_id}/region with the region as the body var data = { dst_region: region, }; toast("Changing region to " + region + "...This may take a few minutes."); fetch(`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/change/region`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", body: JSON.stringify(data), timeOut: 1000, }).then((response) => { if (response.status !== 200) { toast("Failed to change region!"); } else { toast("Region changed successfully! Reloading in 5 seconds.."); // Reload the page in 2 seconds setTimeout(() => { window.location.reload(); }, 5000); } return response.json(); }); }; const RegionChangeModal = () => { // Show from options: "us-west2", "europe-west2", "europe-west3", "northamerica-northeast1" var regions = [ "us-west2", "europe-west2", "europe-west3", "northamerica-northeast1", ]; return ( setRegionChangeModalOpen(false)} aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" > Change region Region ); }; 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 deleteAppAuthenticationGroup = (appAuthGroupId) => { const url = `${globalUrl}/api/v1/authentication/group/${appAuthGroupId}` fetch(url, { method: "DELETE", credentials: "include", headers: { "Content-Type": "application/json", }, }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for deleting app auth group"); } return response.json(); }) .then((responseJson) => { if (responseJson.success === false) { toast("Failed to delete app authentication group"); } else { toast("App authentication group deleted") getAppAuthenticationGroups() } }) .catch((error) => { toast(error.toString()) }) } const createAppAuthenticationGroup = (name, environment, description, appAuthIds) => { // Makes list of ids into a full-on list of auth, but just with the ID // The backend fills in the rest console.log("INput auth: ", appAuthIds) let app_auths = appAuthIds.map((appAuthId) => { return { id: appAuthId }; }) var parsedAppGroup = { label: name, environment: environment, description: description, app_auths: app_auths } if (appAuthenticationGroupId !== undefined && appAuthenticationGroupId !== null && appAuthenticationGroupId !== "") { parsedAppGroup.id = appAuthenticationGroupId } fetch(globalUrl + "/api/v1/authentication/group", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", body: JSON.stringify(parsedAppGroup), }) .then((response) => { if (response.status !== 200) { throw new Error("Failed to create app authentication group"); } return response.json(); }) .then((responseJson) => { if (responseJson.success === false) { toast("Failed to create. Please try again, or contact support@shuffler.io") } else { // Close the modal setAppAuthenticationGroupModalOpen(false) toast("App authentication group created") getAppAuthenticationGroups() } }) .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 handleGetAllTriggers = () => { fetch(globalUrl + "/api/v1/triggers", { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for getting all triggers"); } return response.json(); }) .then((responseJson) => { setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined setAllSchedules(responseJson.schedules || []); setPipelines(responseJson.pipelines || []); }) .catch((error) => { // toast(error.toString()); }); }; 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 { toast("Successfully stopped schedule!"); } setTimeout(handleGetAllTriggers, 1000); }), ) .catch((error) => { console.log("Error in userdata: ", error); }); }; const startSchedule = (trigger) => { if (trigger.name.length <= 0) { toast("Error: name can't be empty"); return; } toast("Creating schedule"); const data = { name: trigger.name, frequency: trigger.frequency, execution_argument: trigger.argument, environment: trigger.environment, id: trigger.id, start: trigger.start_node, }; fetch(`${globalUrl}/api/v1/workflows/${trigger.workflow_id}/schedule`, { method: "POST", 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 stream results :O!"); } return response.json(); }) .then((responseJson) => { if (!responseJson.success) { toast("Failed to set schedule: " + responseJson.reason); } else { toast("Successfully created schedule"); } setTimeout(handleGetAllTriggers, 1000); }) .catch((error) => { //toast(error.toString()); console.log("Get schedule error: ", error.toString()); }); }; const deleteWebhook = (trigger) => { if (trigger === undefined) { return; } fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", { method: "DELETE", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for stream results :O!"); } return response.json(); }) .then((responseJson) => { if (responseJson.success) { toast("Successfully stopped webhook"); } else { if (responseJson.reason !== undefined) { toast("Failed stopping webhook: " + responseJson.reason); } } setTimeout(handleGetAllTriggers, 1000); }) .catch((error) => { toast( "Delete webhook error. Contact support or check logs if this persists.", ); }); }; const startWebHook = (trigger) => { const hookname = trigger.info.name; if (hookname.length === 0) { toast("Missing name"); return; } if (trigger.id.length !== 36) { toast("Missing id"); return; } toast("Starting webhook"); const data = { name: hookname, type: "webhook", id: trigger.id, workflow: trigger.workflows[0], start: trigger.start, environment: trigger.environment, auth: trigger.auth, custom_response: trigger.custom_response, version: trigger.version, version_timeout: 15, }; console.log("Trigger data: ", data); fetch(globalUrl + "/api/v1/hooks/new", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(data), credentials: "include", }) .then((response) => response.json()) .then((responseJson) => { if (responseJson.success) { // Set the status toast("Successfully started webhook"); } else { toast("Failed starting webhook: " + responseJson.reason); } setTimeout(handleGetAllTriggers, 1000); }) .catch((error) => { //console.log(error.toString()); console.log("New webhook error: ", error.toString()); }); }; const changePipelineState = (pipeline, state) => { if (state.trim() === "") { toast("state is not defined"); return; } const data = { name: pipeline.name, id: pipeline.id, type: state, command: pipeline.definition, environment: pipeline.environment, }; if (state === "start") toast("starting the pipeline"); else toast.info("Stopping the pipeline. This may take a few minutes to propagate.") const url = `${globalUrl}/api/v1/triggers/pipeline`; fetch(url, { method: "POST", 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 stream results :O!"); toast("Failed to update the pipeline state"); } return response.json(); }) .then((responseJson) => { if (!responseJson.success) { toast.error("Failed to update the pipeline: " + responseJson.reason); } else { setTimeout(() => { handleGetAllTriggers() }, 5000) setTimeout(() => { handleGetAllTriggers() }, 20000) setTimeout(() => { handleGetAllTriggers() }, 120000) /* if (state === "start") { toast("Successfully created pipeline"); } else { toast("Sucessfully stopped the pipeline"); } */ } }) .catch((error) => { //toast(error.toString()); console.log("Get schedule error: ", error.toString()); }); }; 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, }; toast("Verifying 2fa code. Please wait..."); 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) { if (responseJson.MFAActive === true) { toast.success("Successfully enabled 2fa"); } if (responseJson.MFAActive === false) { toast.success("Successfully disabled 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) { // Check if .reason exists if (responseJson.reason !== undefined) { toast("Failed changing authentication: " + responseJson.reason); } else { toast("Failed changing authentication"); } } else { //toast("Successfully password!") setSelectedUserModalOpen(false); getAppAuthentication(); setSelectedAuthentication({}); setSelectedAuthenticationModalOpen(false); } }), ) .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 if (responseJson.success === false) { toast( "Failed to deactivate user. Please contact support@shuffler.io if this persists.", ); } 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 && foundorgid.length === 36) { 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? localStorage.setItem("globalUrl", ""); localStorage.setItem("getting_started_sidebar", "open"); fetch(`${globalUrl}/api/v1/orgs/${orgId}`, { method: "GET", credentials: "include", headers: { "Content-Type": "application/json", }, }) .then((response) => { if (response.status === 401) { } else { localStorage.removeItem("apps") localStorage.removeItem("workflows") localStorage.removeItem("userinfo") } 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.testing_shuffle) { leads.push("testing shuffle"); } 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 handleGetSubOrgs = (orgId) => { if (orgId.length === 0) { toast( "Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.", ); return; } fetch(`${globalUrl}/api/v1/orgs/${orgId}/suborgs`, { method: "GET", credentials: "include", headers: { "Content-Type": "application/json", }, }) .then((response) => { if (!response.ok) { throw new Error("Failed to fetch sub organizations"); } return response.json(); }) .then((responseJson) => { if (responseJson.success === false) { //toast("Failed getting your org. If this persists, please contact support."); } else { const { subOrgs, parentOrg } = responseJson; setSubOrgs(subOrgs); setParentOrg(parentOrg); } }) .catch((error) => { console.log("Error getting sub orgs: ", error); //toast("Error getting sub organizations"); }); }; const handleClickChangeOrg = (orgId) => { // Don't really care about the logout //name: org.name, //orgId = "asd" const data = { org_id: orgId, }; localStorage.setItem("globalUrl", ""); localStorage.setItem("getting_started_sidebar", "open"); fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, { mode: "cors", credentials: "include", crossDomain: true, method: "POST", body: JSON.stringify(data), withCredentials: true, headers: { "Content-Type": "application/json; charset=utf-8", }, }) .then(function (response) { if (response.status !== 200) { console.log("Error in response"); } else { localStorage.removeItem("apps") localStorage.removeItem("workflows") localStorage.removeItem("userinfo") } return response.json(); }) .then(function (responseJson) { if (responseJson.success === true) { if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) { localStorage.setItem("globalUrl", responseJson.region_url) //globalUrl = responseJson.region_url } setTimeout(() => { window.location.reload() }, 2000); toast.success("Successfully changed active organization - refreshing!"); if (responseJson.org_id !== undefined && responseJson.org_id !== null && responseJson.org_id.length === 36) { navigate(`/admin?org_id=${responseJson.org_id}`) } else { if (orgId !== undefined && orgId !== null && orgId?.includes("@")) { navigate(`/admin`) } else { toast("No pivot?") } } } else { if (responseJson.reason !== undefined && responseJson.reason !== null) { if (!responseJson.reason.includes("already")) { toast("Failed changing org: " + responseJson.reason); } } else { toast("Failed changing org") } } }) .catch((error) => { console.log("error changing: ", error); //removeCookie("session_token", {path: "/"}) }); }; const 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("Changing default env") 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 runs in the background. Check the /debug view to see the progress."); 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"); 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 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 handleDeleteAccount = (userID) => { if (userID === undefined || userID === null || userID === "") { return; } const url = `${globalUrl}/api/v1/users/${userID}/remove`; fetch(url, { mode: "cors", method: "DELETE", credentials: "include", crossDomain: true, withCredentials: true, headers: { "Content-Type": "application/json", }, }) .then((response) => response.json()) .then((data) => { if (data.success) { toast.success( "Deleted their account. Would reload users in a few seconds.", ); setTimeout(() => { getUsers(); }); } else { toast.error(`${data.reason}`); } }) .catch((error) => { console.error( "There was a problem with deleting the account. Please try again:", error, ); toast.error( "There was a problem with the delete request. Please try again", ); }); }; 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 getAppAuthenticationGroups = () => { //console.log("DEBUG: Skipping app auth group loading") //return fetch(globalUrl + "/api/v1/authentication/group", { 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) { setAppAuthenticationGroups(responseJson.data); } }) .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); // 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 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: "triggers", 6: "locations", 7: "suborgs", }; const admin_views = { 0: "organization", 1: "cloud_sync", 2: "priorities", 3: "billing", 4: "partner", }; 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(); getAppAuthenticationGroups(); } 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 if (responseJson.success === false) { toast("Failed to update user"); } 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.replaceAll("_", " ")} ( {selectedAuthentication.label}) You can not see the previous values for an authentication while editing. This is to keep your data secure. You can overwrite one- or multiple fields at a time. Authentication Label { selectedAuthentication.label = e.target.value; }} /> {selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app" ? (
Only the name and url can be modified for Oauth2/OpenID connect. Please remake the authentication if you want to change the other fields like Client ID, Secret, Scopes etc.
) : null} {selectedAuthentication.fields.map((data, index) => { var fieldname = data.key.replaceAll("_", " "); if (fieldname.endsWith(" basic")) { fieldname = fieldname.substring(0, fieldname.length - 6); } if ( selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app" ) { if (selectedAuthentication.fields[index].key !== "url") { return null; } } //console.log("DATA: ", data, selectedAuthentication) return (
{fieldname} { 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.platformColor, color: "white", minWidth: "800px", minHeight: "320px", padding: 50, }, }} > Editing {selectedUser.username} {isCloud ? null : (
{ setNewUsername(e.target.value); }} />
)} {isCloud ? null : (
setNewPassword(e.target.value)} />
)} {userOrgEdit}
{isCloud && userdata.support && selectedUser.id != userdata.id ? ( ) : null} {showDeleteAccountTextbox ? ( { setDeleteAccountText(e.target.value); }} /> ) : null}
{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 factor img ) : ( )} 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); }} onKeyPress={(event) => { if (event.key === 'Enter' && event.target.value.length === 6) { handleVerify2FA(userdata.id, event.target.value, false); } }} />
) : null}
); const GridItem = (props) => { const [expanded, setExpanded] = React.useState(false); const [showEdit, setShowEdit] = React.useState(false); const [newValue, setNewValue] = React.useState(-100); const primary = props.data.primary; const secondary = props.data.secondary; const primaryIcon = props.data.icon; const secondaryIcon = props.data.active ? ( ) : ( ); const submitFeatureEdit = (sync_features) => { if (!userdata.support) { console.log( "User does not have support access and can't edit features", ); return; } sync_features.editing = true; const data = { org_id: selectedOrganization.id, sync_features: sync_features, }; 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 { toast("Successfully edited org!"); } }), ) .catch((error) => { toast("Err: " + error.toString()); }); }; const enableFeature = () => { console.log("Enabling " + primary); console.log(selectedOrganization.sync_features); // Check if primary is in sync_features var tmpprimary = primary.replaceAll(" ", "_"); if (!(tmpprimary in selectedOrganization.sync_features)) { console.log("Primary not in sync_features: " + tmpprimary); return; } if (props.data.active) { selectedOrganization.sync_features[tmpprimary].active = false; } else { selectedOrganization.sync_features[tmpprimary].active = true; } setSelectedOrganization(selectedOrganization); forceUpdate(Math.random()); submitFeatureEdit(selectedOrganization.sync_features); }; const submitEdit = (e) => { e.preventDefault(); e.stopPropagation(); // Check if primary is in sync_features var tmpprimary = primary.replaceAll(" ", "_"); if (!(tmpprimary in selectedOrganization.sync_features)) { console.log("Primary not in sync_features: " + tmpprimary); return; } // Make it into a number var tmp = parseInt(newValue); if (isNaN(tmp)) { console.log("Not a number: " + newValue); return; } selectedOrganization.sync_features[tmpprimary].limit = tmp; setSelectedOrganization(selectedOrganization); forceUpdate(Math.random()); submitFeatureEdit(selectedOrganization.sync_features); }; return ( { setExpanded(!expanded); }} > {primaryIcon} {isCloud && userdata.support === true ? ( { e.preventDefault(); e.stopPropagation(); if (showEdit) { setShowEdit(false); return; } console.log("Edit"); setShowEdit(true); }} /> ) : null} { if (!isCloud || userdata.support !== true) { return; } e.preventDefault(); e.stopPropagation(); enableFeature(); }} > {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} {showEdit ? ( { console.log("Submit"); submitEdit(e); }} > { setNewValue(event.target.value); }} /> ) : 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
); var regiontag = "eu"; if ( userdata.region_url !== undefined && userdata.region_url !== null && userdata.region_url.length > 0 ) { const regionsplit = userdata.region_url.split("."); if (regionsplit.length > 2 && !regionsplit[0].includes("shuffler")) { const namesplit = regionsplit[0].split("/"); regiontag = namesplit[namesplit.length - 1]; if (regiontag === "california") { regiontag = "us"; } } } const organizationView = curTab === 0 && selectedOrganization.id !== undefined ? (

Organization overview

On this page organization admins can configure organizations, and sub-orgs (MSSP).{" "} Learn more
{selectedOrganization.id === undefined ? (
Loading Organization
) : (
{/* { console.log("Should go to icon") }} > */} {userdata.support === true ? ( {/**/} Status ) : null} {isCloud ? ( { if (userdata.support === false) { toast( "Region change is not directly implemented yet, and requires support help.", ); if (window.drift !== undefined) { window.drift.api.startInteraction({ interactionId: 386411, }); } } else { // Show region change modal console.log("Should open region change modal"); setRegionChangeModalOpen(true); } }} > {regiontag} ) : 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 /> Limits & Cloud Sync /> Notifications /> Billing & Stats /> Partner /> {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}
)} Features Features and Limitations that are currently available to you in your Cloud or Hybrid Organization. App Executions (App Runs) reset monthly. If the organization is a customer or in a trial, these features limitations are not always enforced. {selectedOrganization.sync_features === undefined || selectedOrganization.sync_features === null ? null : Object.keys(selectedOrganization.sync_features).map( function (key, index) { if ( key === "schedule" || key === "apps" || key === "updates" || key === "editing" ) { return null; } const item = selectedOrganization.sync_features[key]; if (item === null || item === undefined) { return null; } const newkey = key.replaceAll("_", " "); // Name rewrites as these are structs var newname = ""; if (newkey.toLowerCase() === "shuffle gpt") { newname = "Shuffle AI"; } 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: , newname: newname, }; 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 UpdateMFAInUserOrg = (org_id) => { if (MFARequired === false) { toast("Making MFA required for your organization. Please wait..."); } else { toast("Making MFA optional for your organization. Please wait..."); } const data = { mfa_required: !selectedOrganization.mfa_required, org_id: selectedOrganization.id, } const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; fetch(url, { mode: "cors", method: "POST", body: JSON.stringify(data), credentials: "include", crossDomain: true, withCredentials: true, headers: { "Content-Type": "application/json; charset=utf-8", }, }) .then((response) => response.json().then((responseJson) => { console.log(responseJson) if (responseJson["success"] === false) { toast.error("Failed updating org: ", responseJson.reason); } else { if (MFARequired === false) { setMFARequired(true) toast.success("Successfully make MFA required for your organization!"); } else { setMFARequired(false) toast.success("Successfully make MFA optional for your organization!") } } }), ) .catch((error) => { toast("Err: " + error.toString()); }); } const usersView = curTab === 1 ? (

User Management

Add, edit, block or change passwords.{" "} Learn more
MFA Required { UpdateMFAInUserOrg(selectedOrganization.id); }} />
{logsViewModal ? ( { setLogsViewModal(false); }} PaperProps={{ style: { backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: "1200px", minHeight: "320px", }, }} > User Logs {/* ask user for which IP they want to see logs for by iterating of user.login_info */} User IP {logsLoading && ipSelected.length !== 0 ? (
Loading logs
) : null} {logs.map((data, index) => { //console.log("LOG: ", data) return ( // redirect user to logs // using request id or trace id )})}
) : null} {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 + ")"; } } var userData = data.username; if (userdata.support === true) { userData = ( { setLogsViewModal(true); setUserLogViewing(data); if (userLogViewing.login_info !== undefined && userLogViewing.login_info !== null && userLogViewing.login_info.length > 0) { getLogs(userLogViewing.login_info[0].ip, userLogViewing.id) setIpSelected(userLogViewing.login_info[0].ip); } }} > {data.username} ); } return ( { 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 (MFARequired === true && (selectedUser.mfa_info && selectedUser.mfa_info.active === true)) { toast("MFA is required for your organization. You can't disable it."); return; } 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
{allSchedules === undefined || allSchedules === null || allSchedules.length === 0 ? (
No schedules found.
) : ( {allSchedules.map((schedule, index) => { var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } return ( 0 ? ( schedule.frequency ) : ( {schedule.seconds} seconds ) } /> {schedule.workflow_id} } /> ); })} )}

Webhooks

Current HTTP Webhook endpoints in your organization.  Learn more
{webHooks === undefined || webHooks === null || webHooks.length === 0 ? (
No webhooks found.
) : ( {webHooks.map((webhook, index) => { var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } return ( {webhook.workflows[0]} } /> { 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(webhook.info.url); copyText.select(); copyText.setSelectionRange( 0, 99999, ); /* For mobile devices */ /* Copy the text inside the text field */ document.execCommand("copy"); toast("URL copied to clipboard"); } }} > ) } /> ); })} )}

Pipelines

Controls a pipeline to e.g. connect to Kafka queues or search in the Tenzir storage.{" "} Learn more
{pipelines === undefined || pipelines === null || pipelines.length === 0 ? (
No pipelines found.
) : ( {pipelines.map((pipeline, index) => { var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } return ( ); })} )}
) : 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 handleAppAuthGroupCheckbox = (data) => { //let groupApp = data.app.id var newappauth = appsForAppAuthGroup if (appsForAppAuthGroup.includes(data.app.id)) { newappauth = newappauth.filter((item) => item !== data.app.id) } if (appsForAppAuthGroup.includes(data.id)) { // Remove app from app auth group newappauth = newappauth.filter((item) => item !== data.id) setAppsForAppAuthGroup(newappauth) return } for (var i = 0; i < authentication.length; i++) { if (authentication[i].id === data.id) { continue } if (!appsForAppAuthGroup.includes(authentication[i].id)) { continue } if (authentication[i].app.id === data.app.id) { // Remove app from app auth group newappauth = newappauth.filter((item) => item !== authentication[i].id) toast(`App ${data.app.name} is already in this group`) } } setAppsForAppAuthGroup(newappauth.concat(data.id)) } const KMSItem = (props) => { const { data, index, } = props const [showEnvironmentDropdown, setShowEnvironmentDropdown] = React.useState(false) var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } const isDistributed = data.suborg_distributed === true ? true : false; const isKms = data.label !== undefined && data.label !== null && data.label.toLowerCase() === "kms shuffle storage" var selectedEnvironment = "" if (data.environment !== undefined && data.environment !== null && data.environment.length > 0) { selectedEnvironment = data.environment } if (selectedEnvironment === "" && environments !== undefined && environments !== null && environments.length > 0) { for (var i = 0; i < environments.length; i++) { if (environments[i].default === true) { selectedEnvironment = environments[i].Name break } } } var validIcon = if (data.validation !== null && data.validation !== undefined && data.validation.valid === false) { if (data.validation.changed_at === 0) { // Warning validIcon = "" // } else { validIcon = } } return ( {validIcon} style={{ minWidth: 65, maxWidth: 65, }} onClick={() => { if (data.validation === null || data.validation === undefined) { return } if (data.validation.workflow_id === undefined || data.validation.workflow_id === null || data.validation.workflow_id.length === 0) { toast.warn("No workflow runs found for this auth yet. Check back later.") return } const url = `/workflows/${data.validation.workflow_id}?execution_id=${data.validation.execution_id}&node=${data.validation.node_id}` window.open(url, "_blank") }} /> style={{ minWidth: 75, maxWidth: 75 }} /> { setShowEnvironmentDropdown(true) if (environments === undefined || environments === null || environments.length === 0) { toast.error("No environments found. Please try again in a second, or reload to configure environment to use for KMS") } }} /> {showEnvironmentDropdown === true && environments !== undefined && environments !== null && environments.length > 0 ? Environment : null}
} style={{ minWidth: 225, maxWidth: 225, overflow: "hidden", }} /> {/* */} {/* */} { return data.key; }) .join(", ") } style={{ minWidth: 140, maxWidth: 140, overflow: "auto", marginRight: 10, }} /> { 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); }} /> )} ) } const authenticationView = curTab === 2 ? (
{/* (appAuthenticationGroupModalOpen : { */} {appAuthenticationGroupModalOpen && ( { setAppAuthenticationGroupModalOpen(false); setAppAuthenticationGroupId("") setAppAuthenticationGroupName("") setAppAuthenticationGroupEnvironment("") setAppAuthenticationGroupDescription("") setAppsForAppAuthGroup([]) }} PaperProps={{ style: { backgroundColor: theme.palette.surfaceColor, color: "white", minWidth: "1000px", minHeight: "320px", padding: 25, paddingLeft: 50, }, }} > App Authentication Groups
Name { setAppAuthenticationGroupName(event.target.value); }} />
Evironment {environments !== undefined && environments !== null && environments.length > 0 ? : Locations failed to load. Please try again }
{/* Show a check box list of all app authentications to add to the auth group */}
{authentication.map((data, index) => { var checked = data.checked if (data.label !== undefined && data.label !== null && data.label.toLowerCase() === "kms shuffle storage") { return null } if (checked === undefined || checked === null) { checked = false } if (appsForAppAuthGroup.includes(data.id)) { checked = true } return (
{ handleAppAuthGroupCheckbox(data) }} name={data.label} disabled={data.app.id in appsForAppAuthGroup} />
} label={data.label} />
) })}
)}

App Authentication

Control the authentication options for individual apps. App Groups are farther down on this page.   Learn more about App Authentication
{/**/} {/* */} {authentication === undefined || authentication === null ? null : authentication.map((data, index) => { //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!", }, ]; } return ( ) })}

App Authentication Groups

Groups of authentication options for subflows.{" "} Learn more about App Authentication Groups
{appAuthenticationGroups.map((data, index) => { var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } if (data.app_auths === undefined || data.app_auths === null) { data.app_auths = [] } return ( {data.app_auths.map((appAuth, index) => { if (appAuth.app.large_image === undefined || appAuth.app.large_image === null || appAuth.app.large_image === "") { const foundImage = authentication.find((auth) => auth.app.id === appAuth.app.id) if (foundImage !== undefined) { appAuth.app.large_image = foundImage.app.large_image appAuth.app.name = foundImage.app.name } } const tooltip = `${appAuth.app.name.replaceAll("_", " ")} (authname: ${appAuth.label})` return ( {appAuth.app.name} ) })}
} style={{ minWidth: 250, maxWidth: 250 }} /> { setAppAuthenticationGroupId(data.id) setAppAuthenticationGroupName(data.label) setAppAuthenticationGroupDescription(data.description) setAppsForAppAuthGroup(data.app_auths.map((appAuth) => appAuth.id)) setAppAuthenticationGroupEnvironment(data.environment) setAppAuthenticationGroupModalOpen(true) }} > { deleteAppAuthenticationGroup(data.id) }} >
} style={{ minWidth: 150, maxWidth: 150 }} /> ); } )}
) : null; const getLogs = (ip, userId) => { setLogsLoading(true); console.log("logs loading: ", logsLoading); fetch(`${globalUrl}/api/v1/users/${userId}/audit?user_ip=${ip}`, { mode: "cors", method: "GET", credentials: "include", crossDomain: true, withCredentials: true, headers: { "Content-Type": "application/json; charset=utf-8", }, }) .then((response) => { return response.json(); }) .then((responseJson) => { console.log("ResponseJSON: ", responseJson); if (responseJson.success === true) { setLogs(responseJson.logs); } else { if ( responseJson.success === false || responseJson.reason !== undefined ) { console.log("Reason given: ", responseJson.reason); toast("Failed getting logs: " + responseJson.reason); setLogs([]); } else { toast("Failed getting logs"); } } console.log("logs loading now: ", logsLoading); setLogsLoading(false); }) .catch((error) => { console.log("Error: ", error); toast("Failed getting logs. Please contact: ", error); console.log("logs loading now: ", logsLoading); setLogsLoading(false); }); }; 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 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 ) { 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; 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="${environment.auth}" \\ -e ENVIRONMENT_NAME="${environment.Name}" \\ -e ORG="${environment.org_id}" \\ -e SHUFFLE_WORKER_IMAGE="ghcr.io/shuffle/shuffle-worker:nightly" \\ -e SHUFFLE_SWARM_CONFIG=run \\ -e SHUFFLE_LOGS_DISABLED=true \\ -e BASE_URL="${newUrl}" \\${addProxy ? "\n -e HTTPS_PROXY=IP:PORT \\" : ""}${skipPipeline ? "\n -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 ? "\n -e HTTPS_PROXY=IP:PORT \\" : ""}${skipPipeline ? "\n -e SHUFFLE_SKIP_PIPELINES=true \\" : ""} ghcr.io/shuffle/shuffle-orborus:latest` return commandData 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"); } } const environmentView = curTab === 6 ? (

Locations

Decides where to run your workflows and actions. Uses Shuffle's Orborus runner to handle queued jobs onprem. Previously "Environments". If you have scale problems, talk to our team: support@shuffler.io.  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; } } const queueSize = environment.queue !== undefined && environment.queue !== null ? environment.queue < 0 ? 0 : environment.queue > 1000 ? ">1000" : environment.queue : 0; const orborusCommandWrapper = () => { // Check the current text const orborusCommand = document.getElementById("orborus_command") if (orborusCommand === undefined || orborusCommand === null) { return getOrborusCommand(environment) } return orborusCommand.textContent } return ( { if (environment.Type === "cloud") { toast("Cloud environments are not configurable. To see what is possible, create a new environment.") return } setListItemExpanded(listItemExpanded === index ? -1 : index) }} > ) : environment.run_type === "docker" ? ( ) : environment.run_type === "k8s" ? ( ) : ( ) } style={{ minWidth: 50, maxWidth: 50, overflow: "hidden", }} /> ) : ( ) } style={{ minWidth: 60, maxWidth: 60, overflow: "hidden", }} /> : environment?.data_lake?.enabled ? ( ) : ( { e.preventDefault() e.stopPropagation() window.open("/detections/Sigma", "_blank") }} > ) } style={{ minWidth: 60, maxWidth: 60, overflow: "hidden", }} /> Not running
) : ( environment.running_ip.split(":")[0] ) ) : ( "N/A" ) } style={{ minWidth: 150, maxWidth: 150, marginRight: 10, overflow: "hidden", }} />
{listItemExpanded === index ? : } {listItemExpanded === index ? (
Your Onprem Orborus instance 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. { setInstallationTab(inputValue) }} aria-label="disabled tabs example" variant="scrollable" scrollButtons="auto" style={{textAlign: "center", marginTop: 25, }} > Verbose (default) /> Scale /> k8s /> {installationTab === 2 ? Check our Kubernetes documentation for more information on how to run Shuffle on Kubernetes. The status of the node will change when connected. : 1. Ensure Docker is installed and the target server can reach '{globalUrl}' } {installationTab === 2 ? null : "2. Run this command on the server you want to run workflows or store Pipeline data on"} {installationTab === 2 ? null :
{getOrborusCommand(environment)}
Configure HTTP Proxies: { if (commandController.proxies === undefined) { commandController.proxies = true } else { commandController.proxies = !commandController.proxies } setCommandController(commandController) setUpdate(Math.random()) }} />
Disable Pipelines & Data Lake: { if (commandController.pipelines === undefined) { commandController.pipelines = true } else { commandController.pipelines = !commandController.pipelines } setCommandController(commandController) setUpdate(Math.random()) }} />
} {installationTab === 2 ? null : 3. Verify if the node is running. Try to refresh the page a little while after running the command. }
) : 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 imagesize = 40; const imageStyle = { width: imagesize, height: imagesize, pointerEvents: "none", }; const organizationsTab = curTab === 7 ? (

Organizations

Control sub organizations (tenants)!{" "} {isCloud ? "You can only make a sub organization if you are a customer of shuffle or running a POC of the platform. Please contact support@shuffler.io to try it out." : ""} .{" "} Learn more
{parentOrg ? (

{" "} Your Parent Organization

{(() => { const image = parentOrg.image === "" ? ( {parentOrg.name} ) : ( {parentOrg.name} ); const bgColor = "#27292d"; return ( ); })()}
) : null} {subOrgs.length > 0 ? (

Sub Organizations of the Current Organization ({subOrgs.length})

{subOrgs.map((data, index) => { const image = data.image === "" ? ( {data.name} ) : ( {data.name} ); var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } return ( ); })}
) : null}

All Your 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 image = data.image === "" ? ( {data.name} ) : ( {data.name} ); var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; } return ( { handleClickChangeOrg(data.id) }} > Change Active Org } /> ) })} ) : 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 /> Triggers /> Locations /> 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;