import React, { useEffect } from "react"; import { makeStyles } from "@mui/styles"; import theme from '../theme.jsx'; import { toast } from "react-toastify" import Chip from '@mui/material/Chip'; import Stack from '@mui/material/Stack'; import SubflowSuggestions from "../components/SubflowSuggestions.jsx"; import { FormControl, InputLabel, Paper, OutlinedInput, Checkbox, Card, Tooltip, FormControlLabel, Typography, Switch, Select, MenuItem, Divider, TextField, Button, Tabs, Tab, Grid, IconButton, Autocomplete, Dialog, DialogTitle, DialogActions, DialogContent, Box } from "@mui/material"; import { ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon, Save as SaveIcon, } from "@mui/icons-material"; const useStyles = makeStyles({ notchedOutline: { borderColor: "#f85a3e !important", }, }) const OrgHeaderexpanded = (props) => { const { userdata, selectedOrganization, setSelectedOrganization, globalUrl, isCloud, adminTab, selectedStatus, setSelectedStatus, isEditOrgTab } = props; const classes = useStyles(); const defaultBranch = "master"; const [orgName, setOrgName] = React.useState(selectedOrganization.name); const [orgDescription, setOrgDescription] = React.useState( selectedOrganization.description ); const [appDownloadUrl, setAppDownloadUrl] = React.useState( selectedOrganization.defaults === undefined ? "https://github.com/frikky/shuffle-apps" : selectedOrganization.defaults.app_download_repo === undefined || selectedOrganization.defaults.app_download_repo.length === 0 ? "https://github.com/frikky/shuffle-apps" : selectedOrganization.defaults.app_download_repo ); const [appDownloadBranch, setAppDownloadBranch] = React.useState( selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.app_download_branch === undefined || selectedOrganization.defaults.app_download_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.app_download_branch ); const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState( selectedOrganization.defaults === undefined ? "https://github.com/frikky/shuffle-apps" : selectedOrganization.defaults.workflow_download_repo === undefined || selectedOrganization.defaults.workflow_download_repo.length === 0 ? "https://github.com/frikky/shuffle-workflows" : selectedOrganization.defaults.workflow_download_repo ); const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState( selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.workflow_download_branch === undefined || selectedOrganization.defaults.workflow_download_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.workflow_download_branch ); const [ssoEntrypoint, setSsoEntrypoint] = React.useState( selectedOrganization.sso_config === undefined ? "" : selectedOrganization.sso_config.sso_entrypoint === undefined || selectedOrganization.sso_config.sso_entrypoint.length === 0 ? "" : selectedOrganization.sso_config.sso_entrypoint ); const [ssoCertificate, setSsoCertificate] = React.useState( selectedOrganization.sso_config === undefined ? "" : selectedOrganization.sso_config.sso_certificate === undefined || selectedOrganization.sso_config.sso_certificate.length === 0 ? "" : selectedOrganization.sso_config.sso_certificate ); const [SSORequired, setSSORequired] = React.useState(selectedOrganization.sso_config === undefined ? false : selectedOrganization.sso_config.SSORequired === undefined ? false : selectedOrganization.sso_config.SSORequired); const [notificationWorkflow, setNotificationWorkflow] = React.useState( selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.notification_workflow === undefined || selectedOrganization.defaults.notification_workflow.length === 0 ? "" : selectedOrganization.defaults.notification_workflow ); const [documentationReference, setDocumentationReference] = React.useState( selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.documentation_reference === undefined || selectedOrganization.defaults.documentation_reference.length === 0 ? "" : selectedOrganization.defaults.documentation_reference ); const [openidClientId, setOpenidClientId] = React.useState( selectedOrganization.sso_config === undefined ? "" : selectedOrganization.sso_config.client_id === undefined || selectedOrganization.sso_config.client_id.length === 0 ? "" : selectedOrganization.sso_config.client_id ); const [openidClientSecret, setOpenidClientSecret] = React.useState( selectedOrganization.sso_config === undefined ? "" : selectedOrganization.sso_config.client_secret === undefined || selectedOrganization.sso_config.client_secret.length === 0 ? "" : selectedOrganization.sso_config.client_secret ); const [openidAuthorization, setOpenidAuthorization] = React.useState( selectedOrganization.sso_config === undefined ? "" : selectedOrganization.sso_config.openid_authorization === undefined || selectedOrganization.sso_config.openid_authorization.length === 0 ? "" : selectedOrganization.sso_config.openid_authorization ); const [openidToken, setOpenidToken] = React.useState( selectedOrganization.sso_config === undefined ? "" : selectedOrganization.sso_config.openid_token === undefined || selectedOrganization.sso_config.openid_token.length === 0 ? "" : selectedOrganization.sso_config.openid_token ) const [uploadRepo, setUploadRepo] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_repo === undefined || selectedOrganization.defaults.workflow_upload_repo.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_repo) const [uploadBranch, setUploadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch === undefined || selectedOrganization.defaults.workflow_upload_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch) const [uploadUsername, setUploadUsername] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_username === undefined || selectedOrganization.defaults.workflow_upload_username.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_username) const [uploadToken, setUploadToken] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_token === undefined || selectedOrganization.defaults.workflow_upload_token.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_token) const [workflows, setWorkflows] = React.useState([]) const [workflow, setWorkflow] = React.useState({}) const getAvailableWorkflows = (trigger_index) => { fetch(globalUrl + "/api/v1/workflows", { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for workflows :O!"); return; } return response.json(); }) .then((responseJson) => { if (responseJson !== undefined) { setWorkflows(responseJson) if (selectedOrganization.defaults !== undefined && selectedOrganization.defaults.notification_workflow !== undefined) { const workflow = responseJson.find((workflow) => workflow.id === selectedOrganization.defaults.notification_workflow) if (workflow !== undefined && workflow !== null) { setWorkflow(workflow) } } } }) .catch((error) => { console.log("Error getting workflows: " + error); }) } useEffect(() => { getAvailableWorkflows() }, []) const handleEditOrg = ( name, description, orgId, image, defaults, sso_config ) => { const data = { name: name, description: description, org_id: orgId, image: image, defaults: defaults, sso_config: sso_config, }; 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 handleWorkflowSelectionUpdate = (e, isUserinput) => { if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) { console.log("Returning as there's no id") return null } setWorkflow(e.target.value) setNotificationWorkflow(e.target.value.id) toast("Updated notification workflow. Don't forget to save!") } const orgSaveButton = (
); const toggleBetweenRequiredOrOptional = (event) => { if ( ssoEntrypoint === "" && openidAuthorization === "" && openidToken === "" ) { if (!SSORequired) { toast.error( "Please fill in fields for either OpenID connect or SSO before continuing. " ); return; } } else { toast.info("Toggled SSO. Remember to save."); } setSSORequired(event.target.checked); }; const HandleTestSSO = () => { const url = `${globalUrl}/api/v1/orgs/${selectedOrganization?.id}/change`; const data = { org_id: selectedOrganization?.id, sso_test: true, }; fetch(url, { mode: "cors", credentials: "include", crossDomain: true, method: "POST", body: JSON.stringify(data), withCredentials: true, headers: { "Content-Type": "application/json; charset=utf-8", }, }) .then((response) => { if (response.status !== 200) { toast.error( "Failed to test sso. Please try again later or contact support@shuffler.io if issue persist." ); return; } return response.json(); }) .then((responjson) => { if (responjson["reason"] === "SSO_REDIRECT") { setTimeout(() => { toast.info( "Redirecting to SSO login page as SSO is required for this organization." ); window.location.href = responjson["url"]; return; }, 2000); } else { toast.error( "No SSO found for this org. Please set up sso for this org." ); } }) .catch((err) => { console.log("error for sso test is: ", err); }) } return (
Notification Workflow {/* */}
{workflows !== undefined && workflows !== null && workflows.length > 0 ? { if ( option === undefined || option === null || option.name === undefined || option.name === null ) { return "No Workflow Selected"; } const newname = ( option.name.charAt(0).toUpperCase() + option.name.substring(1) ).replaceAll("_", " "); return newname; }} options={workflows} fullWidth style={{ backgroundColor: theme.palette.inputColor, height: 50, borderRadius: theme.palette?.borderRadius, }} onChange={(event, newValue) => { console.log("Found value: ", newValue) var parsedinput = { target: { value: newValue } } // For variables if (typeof newValue === 'string' && newValue.startsWith("$")) { parsedinput = { target: { value: { "name": newValue, "id": newValue, "actions": [], "triggers": [], } } } } handleWorkflowSelectionUpdate(parsedinput) }} renderOption={(props, data, state) => { if (data.id === workflow.id) { data = workflow; } return ( {data.image !== undefined && data.image !== null && data.image.length > 0 ? {data.name} : null} Choose {data.name} } placement="bottom"> { var parsedinput = { target: { value: data } } handleWorkflowSelectionUpdate(parsedinput) }} > {data.name} ) }} renderInput={(params) => { return ( ); }} /> : { setNotificationWorkflow(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> }
{orgSaveButton}
Org Documentation reference { setDocumentationReference(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> Workflow Backup Repository Decide where workflows are backed up in a Git repository. Will create logs and notifications if upload fails. The repository and branch must already have been initialized. Files will show up in the root folder in the format 'status_workflowid.json' Repository for workflow backup { setUploadRepo(e.target.value); }} InputProps={{ classes: { notchedOutline: isEditOrgTab ? null : classes.notchedOutline, }, style: { color: "white", }, }} /> Branch { setUploadBranch(e.target.value); }} InputProps={{ classes: { notchedOutline: isEditOrgTab ? null : classes.notchedOutline, }, style: { color: "white", }, }} /> Username { setUploadUsername(e.target.value); }} InputProps={{ classes: { notchedOutline: isEditOrgTab ? null : classes.notchedOutline, }, style: { color: "white", }, }} /> Git token/password { setUploadToken(e.target.value); }} InputProps={{ classes: { notchedOutline: isEditOrgTab ? null : classes.notchedOutline, }, style: { color: "white", }, }} type="password" /> SSO Configuration
Make SAML SSO or OpenID Authentication Required or Optional for Your Organization.
{SSORequired ? "Required" : "Optional"}
You can test your SSO configuration by clicking the button below. Before testing, ensure you have set Open ID Connect or SAML SSO credentials. 0 || ssoCertificate.length > 0 || openidAuthorization.length > 0 || openidClientId.length > 0 ) ? "Please ensure all SSO credentials are set before testing." : "" } >
OpenID connect Client ID 0 } id="outlined-with-placeholder" margin="normal" variant="outlined" placeholder="The OpenID client ID from the identity provider" value={openidClientId} onChange={(e) => { setOpenidClientId(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> Client Secret (optional) 0 } id="outlined-with-placeholder" margin="normal" variant="outlined" placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE" value={openidClientSecret} onChange={(e) => { setOpenidClientSecret(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> Authorization URL { setOpenidAuthorization(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> Token URL { setOpenidToken(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> {/* } */} {/*isCloud ? null : */} SAML SSO (v1.1) SSO Entrypoint (IdP) 0 } id="outlined-with-placeholder" margin="normal" variant="outlined" placeholder="The entrypoint URL from your provider" value={ssoEntrypoint} onChange={(e) => { setSsoEntrypoint(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> SSO Certificate (X509) { setSsoCertificate(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> {isCloud ? ( IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso ) : null} {isCloud ? null : ( App Download URL { setAppDownloadUrl(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> )} {isCloud ? null : ( App Download Branch { setAppDownloadBranch(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> )} {isCloud ? null : ( Workflow Download URL { setWorkflowDownloadUrl(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> )} {isCloud ? null : ( Workflow Download Branch { setWorkflowDownloadBranch(e.target.value); }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, }, style: { color: "white", }, }} /> )}
{orgSaveButton}
{/* {expanded ? : } */}
); }; export default OrgHeaderexpanded;