Merge pull request #1619 from LalitDeore/test

sync onprem features/pages with cloud
This commit is contained in:
Frikky
2025-02-06 16:18:00 +01:00
committed by GitHub
30 changed files with 17669 additions and 70 deletions
+36 -3
View File
@@ -1048,6 +1048,20 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
licensed := shuffle.IsLicensed(ctx, *org)
workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 1000, 0)
if err != nil {
log.Printf("{WARNING] Failed getting apps (getworkflowapps): %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
orgApps := workflowapps
activatedAppIds := []string{}
for _, app := range orgApps {
activatedAppIds = append(activatedAppIds, app.ID)
}
returnValue := shuffle.HandleInfo{
Success: true,
Username: userInfo.Username,
@@ -1069,6 +1083,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
Interests: orgInterests,
Priorities: orgPriorities,
Licensed: licensed,
ActiveApps: activatedAppIds,
}
returnData, err := json.Marshal(returnValue)
@@ -3175,7 +3190,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
err = shuffle.DeployAppToDatastore(ctx, api)
//func DeployAppToDatastore(ctx context.Context, workflowapp WorkflowApp, bucketName string) error {
if err != nil {
log.Printf("Failed adding app to db: %s", err)
log.Printf("[ERROR] Failed adding app to db: %s", err)
resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed adding app to db: %s"}`, err)))
return
@@ -3184,7 +3199,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
// 2. Get all the required code
appbase, staticBaseline, err := shuffle.GetAppbase()
if err != nil {
log.Printf("Failed getting appbase: %s", err)
log.Printf("[ERROR] Failed getting appbase: %s", err)
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false, "reason": "Failed getting appbase code"}`))
return
@@ -4842,7 +4857,7 @@ func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) {
// FIXME - add org check too, and not just owner
// Check workflow.Sharing == private / public / org too
if user.Id != workflow.Owner || len(user.Id) == 0 {
if workflow.OrgId == user.ActiveOrg.Id {
if workflow.OrgId == user.ActiveOrg.Id {
log.Printf("[AUDIT] User %s is accessing workflow %s as admin (public)", user.Username, workflow.ID)
} else {
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (public)", user.Username, workflow.ID)
@@ -5107,6 +5122,13 @@ func initHandlers() {
r.HandleFunc("/api/v1/apps/authentication/{appauthId}/config", shuffle.SetAuthenticationConfig).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/authentication/{appauthId}", shuffle.DeleteAppAuthentication).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/authentication/group", shuffle.AddAppAuthenticationGroup).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/authentication/group", shuffle.GetAppAuthenticationGroup).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/authentication/group/{key}", shuffle.DeleteAppAuthenticationGroup).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/authentication/groups", shuffle.AddAppAuthenticationGroup).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/authentication/groups", shuffle.GetAppAuthenticationGroup).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/authentication/groups/{key}", shuffle.DeleteAppAuthenticationGroup).Methods("DELETE", "OPTIONS")
// Related to use-cases that are not directly workflows.
r.HandleFunc("/api/v1/workflows/usecases/{key}", shuffle.HandleGetUsecase).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/workflows/usecases", shuffle.LoadUsecases).Methods("GET", "OPTIONS")
@@ -5173,9 +5195,11 @@ func initHandlers() {
r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/triggers/github/register", shuffle.HandleNewGithubRegister).Methods("PUT", "OPTIONS")
//r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS")
r.HandleFunc("/api/v1/pipelines/{key}", handlePipelineCallback).Methods("POST", "GET", "PATCH", "PUT", "DELETE", "OPTIONS")
r.HandleFunc("/api/v1/triggers", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS")
//r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS")
@@ -5204,8 +5228,13 @@ func initHandlers() {
// This is a new API that validates if a key has been seen before.
// Not sure what the best course of action is for it.
r.HandleFunc("/api/v1/getenvironments", shuffle.HandleGetEnvironments).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/setenvironments", shuffle.HandleSetEnvironments).Methods("PUT", "OPTIONS")
r.HandleFunc("/api/v1/environments/{key}/stop", shuffle.HandleStopExecutions).Methods("GET", "POST", "OPTIONS")
r.HandleFunc("/api/v1/environments/{key}/rerun", shuffle.HandleRerunExecutions).Methods("GET", "POST", "OPTIONS")
r.HandleFunc("/api/v1/environments/{key}/stats", shuffle.HandleGetenvStats).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/environments/{key}/config", shuffle.HandleSetenvConfig).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/environments", shuffle.HandleGetEnvironments).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/validate_app_values", shuffle.HandleKeyValueCheck).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
@@ -5214,8 +5243,10 @@ func initHandlers() {
r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/delete_cache", shuffle.HandleDeleteCacheKeyPost).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache/config", shuffle.HandleCacheConfig).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleAppendStatistics).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/stats/{key}", shuffle.GetSpecificStats).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/statistics", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
@@ -5242,6 +5273,8 @@ func initHandlers() {
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleGetFileMeta).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/files", shuffle.HandleGetFiles).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/files/{fileId}/config", shuffle.HandleSetFileConfig).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/files/namespaces/{namespace}/share", shuffle.HandleShareNamespace).Methods("POST", "OPTIONS")
// This structure is horrendous. Needs fixing after we got the prototype up
r.HandleFunc("/api/v1/detections/{detectionType}/connect", shuffle.HandleDetectionAutoConnect).Methods("GET", "OPTIONS")
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+64 -51
View File
@@ -4,35 +4,36 @@ import { Link, Route, Routes, BrowserRouter, useNavigate } from "react-router-do
import { CookiesProvider } from "react-cookie";
import { removeCookies, useCookies } from "react-cookie";
import Workflows from "./views/Workflows";
import GettingStarted from "./views/GettingStarted";
import Workflows from "./views/Workflows.jsx";
import GettingStarted from "./views/GettingStarted.jsx";
import AngularWorkflow from "./views/AngularWorkflow.jsx";
import Header from "./components/NewHeader.jsx";
import HealthPage from "./components/HealthPage.jsx";
//import Header from "./components/Header.jsx";
import theme from "./theme";
import Apps from "./views/Apps";
import theme from "./theme.jsx";
import Apps from "./views/Apps.jsx";
import Apps2 from "./views/Apps2.jsx";
import AppCreator from "./views/AppCreator";
import AppCreator from "./views/AppCreator.jsx";
import DetectionDashBoard from "./views/DetectionDashboard.jsx";
import Welcome from "./views/Welcome.jsx";
import Dashboard from "./views/Dashboard.jsx";
import DashboardView from "./views/DashboardViews.jsx";
import AdminSetup from "./views/AdminSetup";
import Admin from "./views/Admin";
import AdminSetup from "./views/AdminSetup.jsx";
import Admin from "./views/Admin.jsx";
import Docs from "./views/Docs.jsx";
import Usecases2 from "./views/Usecases2.jsx";
//import Introduction from "./views/Introduction";
import SetAuthentication from "./views/SetAuthentication";
import SetAuthenticationSSO from "./views/SetAuthenticationSSO";
import SetAuthentication from "./views/SetAuthentication.jsx";
import SetAuthenticationSSO from "./views/SetAuthenticationSSO.jsx";
import Search from "./views/Search.jsx";
import RunWorkflow from "./views/RunWorkflow.jsx";
import Admin2 from "./views/Admin2.jsx";
import LoginPage from "./views/LoginPage";
import SettingsPage from "./views/SettingsPage";
import LoginPage from "./views/LoginPage.jsx";
import SettingsPage from "./views/SettingsPage.jsx";
import KeepAlive from "./views/KeepAlive.jsx";
import { ThemeProvider } from "@mui/material/styles";
@@ -40,8 +41,8 @@ import CssBaseline from '@mui/material/CssBaseline';
import UpdateAuthentication from "./views/UpdateAuthentication.jsx";
import FrameworkWrapper from "./views/FrameworkWrapper.jsx";
import ScrollToTop from "./components/ScrollToTop";
import AlertTemplate from "./components/AlertTemplate";
import ScrollToTop from "./components/ScrollToTop.jsx";
import AlertTemplate from "./components/AlertTemplate.js";
import { isMobile } from "react-device-detect";
import RuntimeDebugger from "./components/RuntimeDebugger.jsx"
@@ -58,6 +59,7 @@ import Drift from "react-driftjs";
import { AppContext } from './context/ContextApi.jsx';
import Workflows2 from "./views/Workflows2.jsx";
import AppExplorer from "./views/AppExplorer.jsx";
// Production - backend proxy forwarding in nginx
var globalUrl = window.location.origin;
@@ -207,37 +209,27 @@ const App = (message, props) => {
}
{curpath.includes("/workflows") && curpath.includes("/run") ?
<div style={{ height: 60, }} />
:
isLoggedIn ?
<div style={{ position: 'fixed', top: 16, left: 10, zIndex: 100000 }}>
<LeftSideBar userdata={userdata} globalUrl={globalUrl} serverside={false} notifications={notifications} />
{ window?.location?.pathname === "/" || window?.location?.pathname === "/training" || !(isLoggedIn && isLoaded) ? (
<div style={{ minHeight: 68, maxHeight: 68 }}>
<Header
notifications={notifications}
setNotifications={setNotifications}
userdata={userdata}
cookies={cookies}
removeCookie={removeCookie}
isLoaded={isLoaded}
globalUrl={globalUrl}
setIsLoggedIn={setIsLoggedIn}
isLoggedIn={isLoggedIn}
curpath={curpath}
{...props}
/>
</div>
:
<div style={{ minHeight: 68, maxHeight: 68, }}>
<Header
billingInfo={{}}
notifications={notifications}
setNotifications={setNotifications}
checkLogin={checkLogin}
cookies={cookies}
removeCookie={removeCookie}
isLoaded={isLoaded}
globalUrl={globalUrl}
setIsLoggedIn={setIsLoggedIn}
isLoggedIn={isLoggedIn}
userdata={userdata}
curpath={curpath}
serverside={false}
isMobile={false}
{...props}
/>
</div>
}
) : (
<div style={{ position: 'fixed', top: 32, left: 10, zIndex: 100000 }}>
<LeftSideBar checkLogin={checkLogin} userdata={userdata} globalUrl={globalUrl} notifications={notifications} />
</div>
) }
{/*
<div style={{ height: 60 }} />
@@ -262,7 +254,7 @@ const App = (message, props) => {
/>
<Route
exact
path="/admin"
path="/admin2"
element={
<Admin
userdata={userdata}
@@ -279,6 +271,24 @@ const App = (message, props) => {
/>
}
/>
<Route
exact
path="/admin"
element={
<Admin2
cookies={cookies}
removeCookie={removeCookie}
isLoaded={isLoaded}
isLoggedIn={isLoggedIn}
notifications={notifications}
setNotifications={setNotifications}
globalUrl={globalUrl}
checkLogin={checkLogin}
userdata={userdata}
{...props}
/>
}
/>
<Route exact path="/search" element={<Search serverside={false} isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} {...props} /> } />
<Route
exact
@@ -387,7 +397,7 @@ const App = (message, props) => {
/>
<Route
exact
path="/usecases"
path="/usecases2"
element={
<Dashboard
userdata={userdata}
@@ -400,7 +410,7 @@ const App = (message, props) => {
/>
<Route
exact
path="/usecases2"
path="/usecases"
element={
<Usecases2
userdata={userdata}
@@ -426,7 +436,7 @@ const App = (message, props) => {
<Route exact path="/apps/authentication" element={<UpdateAuthentication serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
<Route
exact
path="/apps"
path="/apps2"
element={
<Apps
isLoaded={isLoaded}
@@ -439,7 +449,7 @@ const App = (message, props) => {
/>
<Route
exact
path="/apps2"
path="/apps"
element={
<Apps2
serverside={false}
@@ -466,7 +476,8 @@ const App = (message, props) => {
/>
}
/>
<Route exact path="/apis/:appid" element={<ApiExplorerWrapper serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} isMobile={false} selectedApp={undefined} isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} checkLogin={checkLogin} {...props} />} />
{/* <Route exact path="/apps/:appid" element={<AppExplorer userdata={userdata} isLoggedIn={isLoggedIn} isLoaded={isLoaded} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} checkLogin={checkLogin} {...props} />} />
<Route exact path="/apis/:appid" element={<ApiExplorerWrapper serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} isMobile={false} selectedApp={undefined} isLoaded={isLoaded}globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} checkLogin={checkLogin} {...props} />} /> */}
<Route
exact
path="/detections/sigma"
@@ -474,7 +485,7 @@ const App = (message, props) => {
/>
<Route
exact
path="/workflows"
path="/workflows2"
element={
<Workflows
checkLogin={checkLogin}
@@ -491,7 +502,7 @@ const App = (message, props) => {
/>
<Route
exact
path="/workflows2"
path="/workflows"
element={
<Workflows2
checkLogin={checkLogin}
@@ -554,6 +565,7 @@ const App = (message, props) => {
isMobile={isMobile}
isLoaded={isLoaded}
globalUrl={globalUrl}
isLoggedIn={isLoggedIn}
{...props}
/>
}
@@ -567,6 +579,7 @@ const App = (message, props) => {
isMobile={isMobile}
isLoaded={isLoaded}
globalUrl={globalUrl}
isLoggedIn={isLoggedIn}
{...props}
/>
}
+227
View File
@@ -0,0 +1,227 @@
import React, { useState, useEffect, useContext, memo } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import OrganizationTab from '../components/OrganizationTab.jsx';
import UserManagmentTab from '../components/UserManagmentTab.jsx';
import CacheView from "../components/CacheView.jsx";
import Files from "../components/Files.jsx";
import AppAuthTab from "../components/AppAuthTab.jsx";
import SchedulesTab from "../components/SchedulesTab.jsx";
import EnvironmentTab from "../components/EnvironmentTab.jsx";
import TenantsTab from "../components/TenantsTab.jsx";
import {
Business as BusinessIcon,
PermIdentity as PermIdentityIcon,
HttpsOutlined as HttpsOutlinedIcon,
InsertDriveFileOutlined as InsertDriveFileOutlinedIcon,
StorageOutlined as StorageOutlinedIcon,
AccessTimeOutlined as AccessTimeOutlinedIcon,
FmdGoodOutlined as FmdGoodOutlinedIcon,
GroupOutlined as GroupOutlinedIcon
} from '@mui/icons-material';
import theme from '../theme.jsx';
import { Button, Tooltip } from '@mui/material';
import { Index } from 'react-instantsearch-dom';
import { Context } from '../context/ContextApi.jsx';
const AdminNavBar = (props) => {
const location = useLocation();
const { globalUrl, userdata, isCloud, isLoaded,removeCookie, handleStatusChange, selectedStatus, setSelectedStatus, handleEditOrg, serverside, notifications, handleGetOrg, orgId, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } = props;
const [selectedItem, setSelectedItem] = useState("Organization");
const [isSelectedFiles, setIsSelectedFiles] = useState(true);
const [isSelectedDataStore, setIsSelectedDataStore] = useState(true);
const navigate = useNavigate();
useEffect(() => {
const queryParams = new URLSearchParams(location.search);
const tabName = queryParams.get('tab');
if (tabName === "environments") {
setSelectedItem("Locations");
} else if (tabName === "suborgs") {
setSelectedItem("Tenants");
}else if (tabName === "cache") {
setSelectedItem("Datastore");
}else if (tabName) {
setSelectedItem(tabName.charAt(0).toUpperCase() + tabName.slice(1));
} else {
setSelectedItem("Organization");
}
}, [location.search]);
const items = [
{ iconSrc: <BusinessIcon />, alt: "Organization Icon", text: "Organization", component: OrganizationTab, props: { globalUrl,removeCookie, selectedStatus, isLoaded, setSelectedStatus, handleStatusChange, handleEditOrg, handleGetOrg, userdata, isCloud, serverside, notifications, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } },
{ iconSrc: <PermIdentityIcon />, alt: "Users Icon", text: "Users", component: UserManagmentTab, props: { globalUrl, userdata, serverside, isCloud, selectedOrganization, setSelectedOrganization, handleEditOrg } },
{ iconSrc: <HttpsOutlinedIcon />, alt: "App Auth Icon", text: "App_auth", component: AppAuthTab, props: { globalUrl, userdata, isCloud, selectedOrganization } },
{ iconSrc: <StorageOutlinedIcon />, alt: "Datastore Icon", text: "Datastore", component: CacheView, props: { globalUrl, userdata, selectedOrganization, serverside, isSelectedDataStore, orgId , isCloud} },
{ iconSrc: <InsertDriveFileOutlinedIcon />, alt: "Files Icon", text: "Files", component: Files, props: { isCloud, globalUrl, userdata, serverside, selectedOrganization, isSelectedFiles } },
{ iconSrc: <AccessTimeOutlinedIcon />, alt: "Trigger Icon", text: "Triggers", component: SchedulesTab, props: { globalUrl, userdata, isCloud, serverside } },
{ iconSrc: <FmdGoodOutlinedIcon />, alt: "Environments Icon", text: "Locations", component: EnvironmentTab, props: { globalUrl, userdata, isCloud, selectedOrganization } },
{ iconSrc: <GroupOutlinedIcon />, alt: "Tenants Icon", text: "Tenants", component: TenantsTab, props: {isCloud, globalUrl, userdata, serverside, selectedOrganization, setSelectedOrganization, checkLogin } }
];
const setConfig = (newValue) => {
setSelectedItem(newValue);
if (newValue === "App Auth") {
const tabName = newValue.toLowerCase().replace(/\s+/g, '_');
navigate(`?tab=${tabName}`, { replace: true });
} else {
const tabName = newValue.toLowerCase().replace(/\s+/g, '_');
navigate(`?tab=${tabName}`, { replace: true });
}
};
const renderComponent = () => {
const selectedItemData = items.find(item => item.text === selectedItem);
if (!selectedItemData) {
setSelectedItem("Organization");
// If no tab is specified, default to "Organization" tab
return <OrganizationTab globalUrl={globalUrl} removeCookie={removeCookie} selectedStatus={selectedStatus} isLoaded={isLoaded} setSelectedStatus={setSelectedStatus} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} userdata={userdata} isCloud={isCloud} serverside={serverside} notifications={notifications} checkLogin={checkLogin} setNotifications={setNotifications} stripeKey={stripeKey} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization}/>;
};
const ComponentToRender = selectedItemData.component;
const componentProps = selectedItemData.props;
return <ComponentToRender {...componentProps} />;
};
const defaultImage = "/images/logos/orange_logo.svg"
const imageData =
selectedOrganization?.image === undefined || selectedOrganization?.image.length === 0
? defaultImage
: selectedOrganization?.image;
return (
<Wrapper>
<div style={{ flexDirection: 'column', width: 220, }}>
<nav style={{ padding: '25px 25px 3px 25px', height: "calc(100% - 30px)", fontSize: '16px', borderTopLeftRadius: 8, borderBottomLeftRadius: 8, background: '#212121', color: '#9CA3AF' }}>
<div style={{ display: 'flex', alignItems: 'center', }}>
<img loading="lazy" src={imageData} alt="Logo" style={{ width: '30px', borderRadius: 8, height: '30px', marginRight: '8px' }} />
<div style={{
fontFamily: theme?.typography?.fontFamily,
fontSize: '16px',
color: "#FFFFFF",
fontWeight: 400,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: '100%',
marginLeft: 5,
}}>{selectedOrganization?.name}</div>
</div>
<div style={{ borderTop: '1px solid #494949', marginTop: 23 }} />
{items.map((item, index) => (
<Tooltip
key={index}
title={
((item.text === "Users") || (item.text === "Files") || (item.text === "Triggers") || (item.text === "Locations")) && !(userdata?.support || userdata?.active_org?.role === "admin")
? "Your role is not admin. Please ask the admin to change your role."
: ""
}
placement="right"
>
<span style={{ display: "inline-block", width: "100%" }}>
<Button
key={item.text}
variant="text"
color="primary"
sx={{
gap: 1,
"&:hover": {
backgroundColor: "#323232 !important",
},
"&.MuiButton-root": {
color: selectedItem === item.text ? "#FFFFFF" : "#9E9E9E",
fontSize: 16,
backgroundColor: "transparent",
textTransform: "none",
cursor: "pointer",
display: "flex",
alignItems: "center",
border: "none",
marginTop: index === 0 ? "15px" : "5px",
width: "100%",
justifyContent: "flex-start",
borderLeft:
selectedItem === item.text
? "3px solid rgba(255, 132, 68, 1)"
: "none",
borderTopLeftRadius: selectedItem === item.text ? "2.5px" : null,
borderBottomLeftRadius: selectedItem === item.text ? "2.5px" : null,
paddingLeft: selectedItem === item.text ? "15px" : "10px",
fontWeight: selectedItem === item.text ? 200 : "normal",
flex: 1,
},
"&.Mui-disabled": {
color: "#6F6F6F",
},
}}
disabled={
((item.text === "Users") || (item.text === "Files") || (item.text === "Triggers") || (item.text === "Locations")) && !(userdata?.support || userdata?.active_org?.role === "admin")
}
startIcon={item.iconSrc}
onClick={() => setConfig(item.text)}
>
{item.text.replace(/_/g, " ")}
</Button>
</span>
</Tooltip>
))}
</nav>
</div>
<Wrapper2>{renderComponent()}</Wrapper2>
</Wrapper>
);
};
export default AdminNavBar;
const PaddingWrapper2 = memo(({ children }) => {
return (
<div div style={{marginBottom: 30, width: "75%" , maxWidth: 1200, height: "100%", boxSizing: 'border-box'}}>
{children}
</div>
)
});
const Wrapper2 = memo(({children}) => {
return (
<PaddingWrapper2>
{children}
</PaddingWrapper2>
);
})
const PaddingWrapper = memo(({ children }) => {
const { leftSideBarOpenByClick, windowWidth } = useContext(Context);
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
paddingLeft: leftSideBarOpenByClick ? windowWidth <= 1300 ? 220 : 200 : 80,
transition: "padding-left 0.3s ease",
width: "100%",
overflow: "hidden",
height: "100%",
}}
>
{children}
</div>
);
});
const Wrapper = memo(({ children }) => {
return (
<PaddingWrapper>
{children}
</PaddingWrapper>
);
})
File diff suppressed because it is too large Load Diff
+12 -8
View File
@@ -416,9 +416,13 @@ const AppGrid = (props) => {
>
{hits.map((data, index) => {
const appUrl =
isCloud
? `/apps/${data.objectID}?queryID=${data.__queryID}`
: `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`;
isCloud === true ?
`/apps/${data.id}`
: `https://shuffler.io/apps/${data.objectID}`;
if (data.name === "" && data.id === "") {
return null
}
return (
<Zoom
@@ -1732,13 +1736,13 @@ const AppGrid = (props) => {
};
const appUrl =
isCloud === true
? `/apps/${data.id}`
isCloud === true ?
`/apps/${data.id}`
: `https://shuffler.io/apps/${data.id}`;
if (data.name === "" && data.id === "") {
return null
}
if (data.name === "" && data.id === "") {
return null
}
return (
<Zoom
+4 -2
View File
@@ -477,7 +477,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
const isCloud =
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io" || window.location.host === "localhost:3000"
window.location.host === "shuffler.io"
? true
: false;
@@ -574,8 +574,10 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
{newAppname}
</Typography>
<Link
to={"/apps/" + (app?.id || app?.objectID)}
to={isCloud ? "/apps/" + (app?.id || app?.objectID) : `https://shuffler.io/apps/${app?.objectID || app?.id}`}
style={{ textDecoration: "none", color: "#f85a3e", marginTop: "-2px" }}
target="_blank"
rel="noopener noreferrer"
>
<IconButton
style={{
+368
View File
@@ -0,0 +1,368 @@
import React, { useState, useEffect } from 'react';
import classNames from "classnames";
import theme from '../theme.jsx';
import {
Tooltip,
TextField,
IconButton,
Button,
Typography,
Grid,
Paper,
Chip,
Checkbox,
} from "@mui/material";
import {
BarChart,
RadialBarChart,
RadialAreaChart,
RadialAxis,
StackedBarSeries,
TooltipArea,
ChartTooltip,
TooltipTemplate,
RadialAreaSeries,
RadialPointSeries,
RadialArea,
RadialLine,
TreeMap,
TreeMapSeries,
TreeMapLabel,
TreeMapRect,
Line,
LineChart,
LineSeries,
LinearYAxis,
LinearXAxis,
LinearYAxisTickSeries,
LinearXAxisTickSeries,
Area,
AreaChart,
AreaSeries,
AreaSparklineChart,
PointSeries,
GridlineSeries,
Gridline,
Stripes,
Gradient,
GradientStop,
LinearXAxisTickLabel,
} from 'reaviz';
const inputdata = {
"data": [{
"key": "Intel",
"data": [
{ key: new Date('11/22/2019'), data: 3, metadata: {color: "green", "name": "Intel"}},
{ key: new Date('11/24/2019'), data: 8, metadata: {color: "green", "name": "Intel"}},
{ key: new Date('11/29/2019'), data: 2, metadata: {color: "green", "name": "Intel"}},
]
},
{
"key": "Popper",
"data": [
{ key: new Date('11/24/2019'), data: 9, metadata: {color: "red", "name": "Popper"}},
{ key: new Date('11/29/2019'), data: 3, metadata: {color: "red", "name": "Popper"}},
]
}]
}
const LineChartWrapper = ({keys, inputname, height, width}) => {
const [hovered, setHovered] = useState("");
//console.log("Date: ", new Date("2019-11-14T08:00:00.000Z"))
//var inputdata = keys.data
//const inputdata = keys.data === undefined ? [{"key": inputname, "data": keys}] : keys.data
const inputdata = keys.data === undefined ? keys : keys.data
/*
series={
<AreaSeries
symbols={
<PointSeries show={false} />
}
area={
<Area
mask={<Stripes />}
gradient={
<Gradient
stops={[
<GradientStop offset="10%" stopOpacity={0} />,
<GradientStop offset="80%" stopOpacity={1} />
]}
/>
}
/>
}
gridlines={<GridlineSeries line={<Gridline direction="x" />} />}
colorScheme={(colorInput) => {
var color = "#f86a3e"
//if (colorInput !== undefined && colorInput.length > 0) {
// color = colorInput[0].metadata !== undefined && colorInput[0].metadata.color !== undefined ? colorInput[0].metadata.color : color
//}
return color
}}
/>
}
*/
return (
<div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, padding: 30, marginTop: 15, }}>
<Typography variant="h4" style={{marginBotton: 15, }}>
{inputname}
</Typography>
<BarChart
width={"100%"}
height={height}
data={inputdata}
gridlines={
<GridlineSeries line={<Gridline direction="all" />} />
}
/>
{/*
<AreaSparklineChart
style={{marginTop: 15, color: "white",}}
height={height}
width={width}
data={inputdata}
tooltip={
<Tooltip
tooltip={
<ChartTooltip
color={"#ffffff"}
followCursor={true}
modifiers={{
offset: '5px, 5px'
}}
content={(data, color) => (
<TooltipTemplate
color={"#ffffff"}
value={{
x: data.x,
y: data.y,
}}
/>
)}
/>
}
/>
}
/>
*/}
</div>
)
}
const AppStats = (defaultprops) => {
const { globalUrl, appId , workflowId} = defaultprops;
const [keys, setKeys] = useState([])
const [widgetData, setWidgetData] = useState({});
const [searches, setSearches] = useState([]);
const [clickData, setClickData] = useState(undefined);
const [conversionData, setConversionData] = useState(undefined);
const handleDataSetting = (inputdata, grouping) => {
var newlist = []
for (var key in inputdata.events) {
var newlist = []
for (var subkey in inputdata.events[key].data) {
const subdata = inputdata.events[key].data[subkey]
//console.log("Timestamp: ", subdata.key)
if (grouping === "day") {
const daysplit = subdata.key.split("T")[0]
//console.log("Grouping by day: ", daysplit)
const foundIndex = newlist.findIndex(data => data.key === daysplit)
if (foundIndex !== undefined && foundIndex !== null && foundIndex >= 0) {
newlist[foundIndex].data += 1
newlist[foundIndex].y += 1
} else {
newlist.push({
"key": daysplit,
"x": daysplit,
"data": 1,
"y": 1,
})
}
} else {
console.log("No grouping set?")
try {
inputdata.events[key].data[subkey].key = new Date(subdata.key)
} catch (e) {
console.log("Failed timestamp: ", e)
}
}
}
// Fixing timestamps after sorting based on day
for (var subkey in newlist) {
const subdata = newlist[subkey]
newlist[subkey].key = new Date(subdata.key)
}
console.log("Inputdata: ", inputdata.events[key])
if (inputdata.events[key].key === "click") {
setClickData(newlist)
} else if (inputdata.events[key].key === "conversion") {
setConversionData(newlist)
} else {
console.log("No handler for ", inputdata.events[key].key)
}
}
//new Date('11/22/2019')
setWidgetData(inputdata)
}
const getAppStats = (appId) => {
fetch(`${globalUrl}/api/v1/apps/${appId}/stats`, {
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!: ", response.status);
return;
}
return response.json();
})
.then((responseJson) => {
if (responseJson["success"] === false) {
return
}
handleDataSetting(responseJson, "day")
})
.catch((error) => {
console.log("error: ", error)
});
}
const getWorkflowStats = (workflowId) => {
fetch(`${globalUrl}/api/v1/workflow/${workflowId}/stats`, {
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!: ", response.status);
return;
}
return response.json();
})
.then((responseJson) => {
if (responseJson["success"] === false) {
return
}
handleDataSetting(responseJson, "day")
})
.catch((error) => {
console.log("error: ", error)
});
}
useEffect(() => {
//setWidgetData(inputdata)
getAppStats(appId)
getWorkflowStats(workflowId)
}, [])
const paperStyle = {
textAlign: "center",
padding: 40,
margin: 5,
backgroundColor: theme.palette.inputColor,
}
console.log("Widget: ", widgetData)
const data = (
<div className="content" style={{width: "100%", margin: "auto", paddingBottom: 200, textAlign: "center",}}>
<div style={{display: "flex", margin: "auto", }}>
<Paper style={paperStyle}>
<Typography variant="h4">
{widgetData.orgs}
</Typography>
<Typography variant="h6">
Orgs
</Typography>
</Paper>
<Paper style={paperStyle}>
<Typography variant="h4">
{widgetData.searches}
</Typography>
<Typography variant="h6">
Searches
</Typography>
</Paper>
{/*
<Paper style={paperStyle}>
<Typography variant="h4">
{widgetData.clicks}
</Typography>
<Typography variant="h6">
Clicks
</Typography>
</Paper>
<Paper style={paperStyle}>
<Typography variant="h4">
{widgetData.conversions}
</Typography>
<Typography variant="h6">
Conversions
</Typography>
</Paper>
<Paper style={paperStyle}>
<Typography variant="h4">
{widgetData.forks}
</Typography>
<Typography variant="h6">
Forks
</Typography>
</Paper>
*/}
</div>
{clickData === undefined ?
null
:
<LineChartWrapper keys={clickData} height={300} width={"100%"} inputname={"Clicks"}/>
}
<div style={{marginTop: 25, }} />
{conversionData === undefined ?
null
:
<LineChartWrapper keys={conversionData} height={300} width={"100%"} inputname={"Conversions"}/>
}
</div>
)
const dataWrapper = (
<div style={{ maxWidth: 1366, margin: "auto" }}>{data}</div>
);
return dataWrapper;
}
export default AppStats;
+780
View File
@@ -0,0 +1,780 @@
import React, { useEffect, useState, useContext } from "react";
import {
FormControl,
Card,
Tooltip,
Typography,
TextField,
Button,
Grid,
ListItem,
ListItemText,
ListItemAvatar,
IconButton,
Avatar,
Zoom,
InputAdornment,
Switch,
Skeleton
} from "@mui/material";
import { ToastContainer, toast } from "react-toastify";
import {
Edit as EditIcon,
Polyline as PolylineIcon,
CheckCircle as CheckCircleIcon,
Close as CloseIcon,
Visibility as VisibilityIcon,
VisibilityOff as VisibilityOffIcon,
} from "@mui/icons-material";
import theme from "../theme.jsx";
import { styled } from '@mui/styles';
import { Context } from "../context/ContextApi.jsx";
const CloudSyncTab = (props) => {
const {
userdata,
globalUrl,
serverside
} = props;
const [cloudSyncApikey, setCloudSyncApikey] = useState("");
const [loading, setLoading] = useState(false);
const [showApiKey, setShowApiKey] = useState(false);
const [orgSyncResponse, setOrgSyncResponse] = React.useState("");
const [organizationFeatures, setOrganizationFeatures] = React.useState({});
const [selectedOrganization, setSelectedOrganization] = React.useState({});
const [selectedStatus, setSelectedStatus] = React.useState([]);
const [orgRequest, setOrgRequest] = React.useState(true);
const [userSettings, setUserSettings] = React.useState({});
const [, forceUpdate] = React.useState();
const itemColor = "white";
const isCloud = window?.location?.host === "localhost:3002" || window?.location?.host === "shuffler.io";
useEffect(() => { getSettings(); }, []);
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 ?
<CheckCircleIcon style={{ color: "green" }} />
:
<CloseIcon style={{ color: "red" }} />
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,
};
console.log("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)
}
const handleToggleFeature = (e) => {
// Your logic for toggling the feature's active state
console.log(`Toggling ${primary}`);
if (!isCloud || userdata.support !== true) {
return
}
e.preventDefault();
e.stopPropagation();
enableFeature()
};
return (
<Grid
item
xs={4}
>
<div
style={{
margin: 4,
backgroundColor: "#1a1a1a",
borderRadius: 8,
color: "white",
minHeight: expanded ? 250 : "inherit",
maxHeight: expanded ? 300 : "inherit",
boxShadow: "none",
}}
>
<ListItem
style={{ cursor: "pointer", }}
onClick={() => {
setExpanded(prev => !prev);
if(showEdit){
setShowEdit(false)
}
}}
>
<ListItemAvatar>
<Avatar>{primaryIcon}</Avatar>
</ListItemAvatar>
<ListItemText
style={{ textTransform: "capitalize", color: "#F1F1F1", fontSize: 14, fontWeight: 400, }}
primary={primary}
/>
{isCloud && userdata.support === true ?
<Tooltip title="Edit features (support users only)">
<EditIcon
color="secondary"
style={{ marginRight: 10, cursor: "pointer", }}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
console.log('expanded', expanded)
if (expanded){
setExpanded(false)
}
if (showEdit) {
setShowEdit(false)
return
}
console.log("Edit")
setShowEdit(true)
}}
/>
</Tooltip>
: null}
{userdata.support === true ?(
<Tooltip title={props.data.active ? 'Disable feature' : 'Enable feature'}>
<Switch
checked={props.data.active}
onChange={handleToggleFeature}
color="primary"
inputProps={{ 'aria-label': 'feature toggle' }}
sx={{
'& .MuiSwitch-switchBase.Mui-checked': {
color: '#FFFFFF',
},
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
backgroundColor: props.data.active ? '#2BC07E' : "#9e9e9e",
},
"& .MuiSwitch-track": {
backgroundColor: props.data.active ? '#2BC07E' : "#9e9e9e"
}
}}
/>
</Tooltip>
):(
<Tooltip title={props.data.active ? "Disable feature" : "Enable feature"}>
<span
style={{ cursor: "pointer", marginTop: 5, }}
onClick={(e) => {
if (!isCloud || userdata.support !== true) {
return
}
e.preventDefault();
e.stopPropagation();
enableFeature()
}}
>
{secondaryIcon}
</span>
</Tooltip>
)}
</ListItem>
{expanded ?
<div style={{ padding: 15 }}>
<Typography>
<b>Usage:&nbsp;</b>
{props.data.limit === 0 ? (
"Unlimited"
) : (
<span>
{props.data.usage} / {props.data.limit === "" ? "Unlimited" : props.data.limit}
</span>
)}
</Typography>
{/*<Typography>
Data sharing: {props.data.data_collection}
</Typography>*/}
<Typography style={{ maxHeight: 150, overflowX: "hidden", overflowY: "auto" }}><b>Description:</b> {secondary}</Typography>
</div>
: null}
{showEdit ?
<FormControl fullWidth onSubmit={(e) => {
console.log("Submit")
submitEdit(e)
}}>
<span style={{ display: "flex",}}>
<TextField
style={{ flex: 3, }}
color="primary"
label={"Edit value"}
defaultValue={props.data.limit}
sx={{
marginTop: 0.5,
marginBottom: 0.5
}}
onChange={(event) => {
setNewValue(event.target.value)
}}
/>
<Button
style={{ flex: 1, }}
variant="contained"
disabled={newValue < -1}
onClick={(e) => {
console.log("Submit 2")
submitEdit(e)
}}
sx={{
marginTop: 0.5,
maarginBottom: 0.5
}}
>
Submit
</Button>
</span>
</FormControl>
: null}
</div>
</Grid>
);
};
const handleGetOrg = (orgId) => {
if (serverside !== true && window.location.search !== undefined && window.location.search !== null) {
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
const foundorgid = params["org_id"];
if (foundorgid !== undefined && foundorgid !== null) {
orgId = foundorgid;
}
}
if (orgId.length === 0) {
toast("Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.");
return;
}
// Just use this one?
fetch(`${globalUrl}/api/v1/orgs/${orgId}`, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.status === 401) {
}
return response.json();
})
.then((responseJson) => {
if (responseJson["success"] === false) {
toast("Failed getting your org. If this persists, please contact support.");
} else {
if (
responseJson.sync_features === undefined ||
responseJson.sync_features === null
) {
responseJson.sync_features = {};
}
setSelectedOrganization(responseJson)
var lists = {
active: {
triggers: [],
features: [],
sync: [],
},
inactive: {
triggers: [],
features: [],
sync: [],
},
};
setOrganizationFeatures(lists);
}
})
.catch((error) => {
console.log("Error getting org: ", error);
toast("Error getting current organization");
});
};
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(); API no longer in use, as it's in handleInfo request
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 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);
});
};
if (
selectedOrganization.id === undefined &&
userdata !== undefined &&
userdata.active_org !== undefined &&
orgRequest === true
) {
setOrgRequest(false);
handleGetOrg(userdata.active_org.id);
}
return (
<div style={{padding: "27px 10px 19px 27px",}}>
<div style={{ marginBottom: 20 }}>
<h2
style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}
>
Cloud syncronization
</h2>
<span style={{ color: "#C8C8C8", fontSize: 16, fontWeight: 400, }}>
What does <a href="/docs/organizations#cloud_sync" target="_blank" rel="noopener noreferrer" style={{ color: "rgba(255, 132, 68, 1)", fontSize: 16, textDecoration: 'none', }}>cloud sync</a> do? Cloud synchronization 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.
</span>
</div>
{isCloud ? (
<div style={{ marginTop: 15, display: "flex" }}>
<div style={{ flex: 1 }}>
<Typography style={{fontWeight: 400, fontSize: 16, color: "#F1F1F1"}}>
Currently syncronizing:{" "}
{selectedOrganization.cloud_sync_active === true
? <span style={{ color: "#4CFD72", fontSize: 16, marginLeft: 16}}>True</span>
: <span style={{ color: "#FD4C62", fontSize: 16, marginLeft: 16 }}>False</span>}
</Typography>
{selectedOrganization.cloud_sync_active ? (
<Typography style={{}}>
Syncronization interval:{" "}
{selectedOrganization.sync_config.interval === 0
? "60"
: selectedOrganization.sync_config.interval}
</Typography>
) : null}
<Typography
style={{
whiteSpace: "nowrap",
marginTop: 25,
marginRight: 10,
fontSize: 16,
fontWeight: 400,
fontFamily: theme.typography.fontFamily,
}}
>
Your Api key
</Typography>
{userSettings?.apikey === undefined || userSettings?.apikey === null || userSettings?.apikey?.length <=0 ? (
<Skeleton variant="rectangular" animation="wave" sx={{backgroundColor: '#212121', border: '1px solid #646464', width: 500, height: 50, marginTop: 2 }}/>
):
<div style={{ display: "flex" }}>
<TextField
color="primary"
style={{
backgroundColor: theme.palette.inputColor,
maxWidth: 500,
height: 35
}}
InputProps={{
sx: {
height: "35px",
color: "white",
fontSize: "1em",
backgroundColor: '#212121',
},
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => {
setShowApiKey(!showApiKey)
}}
>
{showApiKey ? <VisibilityIcon /> : <VisibilityOffIcon />}
</IconButton>
</InputAdornment>
)
}}
required
fullWidth={true}
disabled={true}
autoComplete="cloud apikey"
id="apikey_field"
margin="normal"
placeholder="Cloud Apikey"
variant="outlined"
value={userSettings?.apikey}
defaultValue={userSettings?.apikey}
type={!isCloud || showApiKey ? "text" : "password"}
/>
{selectedOrganization.cloud_sync_active ? (
<Button
style={{
width: 150,
height: 50,
marginLeft: 10,
marginTop: 17,
}}
variant={
selectedOrganization.cloud_sync_active === true
? "outlined"
: "contained"
}
color="primary"
onClick={() => {
handleStopOrgSync(selectedOrganization.id);
}}
>
Stop Sync
</Button>
) : null}
</div>}
</div>
</div>
) : (
<div>
<div style={{ display: "flex", marginBottom: 20 }}>
<TextField
color="primary"
style={{
backgroundColor: "#1a1a1a",
marginRight: 10,
height: 35,
}}
InputProps={{
style: {
height: "35px",
color: "white",
fontSize: "1em",
},
}}
required
fullWidth={true}
disabled={selectedOrganization.cloud_sync}
autoComplete="cloud apikey"
id="apikey_field"
margin="normal"
placeholder="Cloud Apikey"
variant="outlined"
onChange={(event) => {
setCloudSyncApikey(event.target.value);
}}
/>
<Button
disabled={
(!selectedOrganization.cloud_sync &&
cloudSyncApikey.length === 0) ||
loading
}
style={{ marginTop: 15, height: 35, width: 150, textTransform: 'none', fontSize: 16, color: (!selectedOrganization.cloud_sync &&
cloudSyncApikey.length === 0) ||
loading ? null : "#1a1a1a", backgroundColor: (!selectedOrganization.cloud_sync &&
cloudSyncApikey.length === 0) ||
loading? null : "#FF8544" }}
onClick={() => {
setLoading(true);
enableCloudSync(
cloudSyncApikey,
selectedOrganization,
selectedOrganization.cloud_sync
);
}}
color="primary"
variant={
selectedOrganization.cloud_sync === true
? "outlined"
: "contained"
}
>
{selectedOrganization.cloud_sync
? "Stop sync"
: "Start sync"}
</Button>
</div>
{orgSyncResponse.length > 0 ? (
<Typography style={{ marginTop: 5, marginBottom: 10 }}>
Message from Shuffle Cloud: <b>{orgSyncResponse}</b>
</Typography>
) : null}
</div>
)}
<h2 style={{ marginLeft: 5, marginTop: 40, marginBottom: 5 }}>
Features
</h2>
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 24, fontSize: 16, fontWeight: 400, marginLeft: 5, color: "#C8C8C8" }}>
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. </Typography>
<Grid container style={{ width: "100%", marginBottom: 15, }}>
{selectedOrganization.sync_features === undefined ||
selectedOrganization.sync_features === null
? <Grid container spacing={2} justifyContent="center">
{[...Array(18)].map((_, i) => (
<Grid item xs={12} sm={6} md={4} key={i}>
<div
style={{
margin: 4,
borderRadius: 8,
minHeight: "inherit",
maxHeight: "inherit",
boxShadow: "none",
display: 'flex',
justifyContent: 'center',
}}
>
<Skeleton
variant="rectangular"
height={50}
width={343}
sx={{ backgroundColor: '#1a1a1a', display: 'flex', borderRadius: 1 }}
animation="wave"
/>
</div>
</Grid>
))}
</Grid>
: 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) {
return null
}
const newkey = key.replaceAll("_", " ");
const griditem = {
primary: newkey,
secondary:
item.description === undefined ||
item.description === null ||
item.description.length === 0
? "Not defined yet"
: item.description,
limit: item.limit,
usage: item.usage === undefined ||
item.usage === null ? 0 : item.usage,
data_collection: "None",
active: item.active,
icon: <PolylineIcon style={{ color: "#1a1a1a" }} />,
};
return (
<Zoom key={index}>
<GridItem data={griditem} />
</Zoom>
);
})}
</Grid>
</div>
);
};
export default CloudSyncTab;
+446
View File
@@ -0,0 +1,446 @@
import React, { useEffect, useState, useContext } from 'react';
import OrgHeaderexpanded from "./OrgHeaderexpandedNew.jsx";
import OrgHeader from './OrgHeaderNew.jsx';
import { toast } from "react-toastify";
import CloudSyncTab from './CloudSyncTab.jsx';
import {
FileCopy as FileCopyIcon,
} from "@mui/icons-material";
import {
Button,
Tooltip,
IconButton,
} from "@mui/material";
const EditOrgTab = (props) => {
const {
userdata,
globalUrl,
serverside,
selectedOrganization,
setSelectedOrganization,
handleGetOrg,
selectedStatus, setSelectedStatus,
handleEditOrg,
} = props;
const [organizationFeatures, setOrganizationFeatures] = React.useState({});
const [users, setUsers] = React.useState([]);
const [orgRequest, setOrgRequest] = React.useState(true);
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
useEffect(() => {
if(users.length === 0) {
getUsers();
}
}, []);
const handleStatusChange = (event) => {
const { value } = event.target;
setSelectedStatus(value);
handleEditOrg(
selectedOrganization?.name,
selectedOrganization?.description,
selectedOrganization.id,
selectedOrganization.image,
{
app_download_repo: selectedOrganization?.defaults?.app_download_repo,
app_download_branch: selectedOrganization?.defaults?.app_download_branch,
workflow_download_repo: selectedOrganization?.defaults?.workflow_download_repo,
workflow_download_branch: selectedOrganization?.defaults?.workflow_download_branch,
notification_workflow: selectedOrganization?.defaults?.notification_workflow,
documentation_reference: selectedOrganization?.defaults?.documentation_reference,
workflow_upload_repo: selectedOrganization?.defaults?.workflow_upload_repo,
workflow_upload_branch: selectedOrganization?.defaults?.workflow_upload_branch,
workflow_upload_username: selectedOrganization?.defaults?.workflow_upload_username,
workflow_upload_token: selectedOrganization?.defaults?.workflow_upload_token,
newsletter: selectedOrganization?.defaults?.newsletter,
weekly_recommendations: selectedOrganization?.defaults?.weekly_recommendations,
},
{
sso_entrypoint: selectedOrganization?.sso_config?.sso_entrypoint,
sso_certificate: selectedOrganization?.sso_config?.sso_certificate,
client_id: selectedOrganization?.sso_config?.client_id,
client_secret: selectedOrganization?.sso_config?.client_secret,
openid_authorization: selectedOrganization?.sso_config?.openid_authorization,
openid_token: selectedOrganization?.sso_config?.openid_token,
SSORequired: selectedOrganization?.sso_config?.SSORequired,
auto_provision: selectedOrganization?.sso_config?.auto_provision,
},
value.length === 0 ? ["none"] : value,
);
};
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 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}`
}
if (
selectedOrganization.id === undefined &&
userdata !== undefined &&
userdata.active_org !== undefined &&
orgRequest
) {
setOrgRequest(false);
}
return (
<div style={{ width: "100%", boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: '#212121', borderRadius: '16px', }}>
<div style={{ height: "100%", width: "100%", overflowX: 'hidden', scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}} >
<div style={{ marginBottom: 20 }}>
<div style={{display:"flex"}}>
<div style={{width:'70%'}}>
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}>Organization overview</h2>
<span style={{ color: "#9E9E9E" }}>
On this page organization admins can configure organisations, and sub-orgs (MSSP).{" "}
<a
target="_blank"
rel="noopener noreferrer"
href="/docs/organizations#organization"
style={{ color: "#FF8444" }}
>
Learn more
</a>
</span>
</div>
<div style={{display:"flex", alignItems:"center", marginLeft:50}}>
<Tooltip
title="Copy Organization ID"
aria-label="Copy orgid"
>
<IconButton
style={{
display: "flex",
alignItems: "center",
width: 40,
height: 40,
backgroundColor: "rgba(47, 47, 47, 1)",
borderRadius: 200
}}
onClick={() => {
const org_id = selectedOrganization.id;
// Check if organization ID exists
if (!org_id) {
toast("No organization ID found");
return;
}
// Use clipboard API
navigator.clipboard.writeText(org_id)
.then(() => {
toast.success(`${org_id} copied to clipboard`);
})
.catch((error) => {
// Fallback for browsers that don't support clipboard API
try {
// Create temporary input element
const tempInput = document.createElement('input');
tempInput.value = org_id;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand('copy');
document.body.removeChild(tempInput);
toast(`${org_id} copied to clipboard`);
} catch (err) {
toast("Failed to copy. Please try again.");
console.error("Copy failed:", err);
}
});
}}
>
<FileCopyIcon style={{ color: "rgba(255,255,255,0.8)" }} />
</IconButton>
</Tooltip>
{userdata.support === true ?
<span style={{ display: "flex", alignItems: "center", marginLeft:16 }}>
{/*<a href={mailsendingButton(selectedOrganization)} target="_blank" rel="noopener noreferrer" style={{textDecoration: "none"}} disabled={selectedStatus.length !== 0}>*/}
<Button
// variant="outlined"
// color="primary"
// disabled={selectedStatus.length !== 0}
style={{
width: 180,
height: 40,
borderRadius: 4,
border: "1.5px solid #ff8544",
background: "transparent",
color: "#ff8544",
fontSize: 16,
textTransform: "none",
boxShadow: "none",
cursor: "pointer",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
}}
onClick={() => {
console.log("Should send mail to admins of org with context")
handleStatusChange({ target: { value: ["contacted"] } })
// Open a new tab
window.open(mailsendingButton(selectedOrganization), "_blank")
}}
>
Sales mail
</Button>
</span>
: null}
</div>
</div>
{/* {isCloud ?
<Tooltip
title={`Your organization is in ${regiontag}. Click to change!`}
style={{
}}
>
<Avatar
style={{ cursor: "pointer", top: -10, right: 50, position: "absolute", }}
onClick={() => {
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}
</Avatar>
</Tooltip>
: null} */}
</div>
<OrgHeader
isCloud={isCloud}
userdata={userdata}
setSelectedOrganization={setSelectedOrganization}
globalUrl={globalUrl}
selectedOrganization={selectedOrganization}
handleEditOrg={handleEditOrg}
isEditOrgTab={true}
handleGetOrg={handleGetOrg}
/>
<OrgHeaderexpanded
isCloud={isCloud}
userdata={userdata}
selectedStatus={selectedStatus}
setSelectedStatus={setSelectedStatus}
setSelectedOrganization={setSelectedOrganization}
globalUrl={globalUrl}
selectedOrganization={selectedOrganization}
isEditOrgTab={true}
handleGetOrg={handleGetOrg}
serverside={serverside}
/>
</div>
</div >
)
}
export default EditOrgTab;
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+222
View File
@@ -0,0 +1,222 @@
import React, { useEffect, useState, useCallback } from 'react';
import { Link, useNavigate, useLocation } from "react-router-dom";
import Billing from "../components/Billing.jsx";
import Priorities from "../components/Priorities.jsx";
import Branding from "../components/Branding.jsx";
// import AnalyticsTab from '../components/AnalyticsTab.jsx';
import EditOrgTab from '../components/EditOrgTab.jsx';
import CloudSyncTab from '../components/CloudSyncTab.jsx';
import SSOTab from "../components/ssoTab.jsx"
import { ToastContainer, toast } from "react-toastify";
import { Button, Tooltip } from '@mui/material';
const OrganizationTab = (props) => {
const location = useLocation();
const navigate = useNavigate();
const {
userdata,
globalUrl,
serverside,
isCloud,
checkLogin,
notifications,
setNotifications,
stripeKey, setSelectedOrganization,
selectedStatus, setSelectedStatus,
selectedOrganization, handleGetOrg,
handleStatusChange, handleEditOrg,
isLoaded,
removeCookie
} = props;
const [selectedTab, setSelectedTab] = useState('org_config');
const [organizationFeatures, setOrganizationFeatures] = useState({});
const [billingInfo, setBillingInfo] = useState({});
const [orgRequest, setOrgRequest] = React.useState(true);
const [curIndex, setCurIndex] = React.useState(0);
const [unreadNotifications, setUnreadNotifications] = React.useState(
notifications?.filter((notification) => notification.read === false)?.length
);
useEffect(() => {
const queryParams = new URLSearchParams(location.search);
const tabName = queryParams.get('admin_tab');
if (tabName) {
const decodedTabName = decodeURIComponent(tabName);
setSelectedTab(decodedTabName);
if (decodedTabName === 'org_config') {
setCurIndex(0);
} else if(decodedTabName === 'sso'){
setCurIndex(1)
}else if (decodedTabName === 'notifications' || decodedTabName === 'priorities') {
setCurIndex(2);
} else if (decodedTabName === 'billingstats' || decodedTabName === 'billing') {
setCurIndex(3);
} else if (decodedTabName === 'branding(beta)') {
setCurIndex(4);
}
// else if (decodedTabName === 'analytics') {
// setCurIndex(5);
// }
}
}, [location.search]);
const handleTabClick = (tabName) => {
const formattedTabName = tabName.toLowerCase().replace(/[\s&]+/g, '');
const encodedTabName = encodeURIComponent(formattedTabName);
setSelectedTab(formattedTabName);
document.title = `Shuffle - admin - ${formattedTabName}`;
navigate(`?admin_tab=${encodedTabName}`);
};
const handleNotifications = useCallback(() => {
const unreadCount = notifications?.filter((notification) => notification.read === false).length;
setUnreadNotifications(unreadCount);
},[unreadNotifications,notifications]);
useEffect(() => {
if ((unreadNotifications !== notifications?.filter((notification) => notification.read === false).length) !== unreadNotifications) {
handleNotifications();
}
}, [notifications]);
const renderContent = () => {
switch (selectedTab) {
case 'org_config':
return <EditOrgTab isCloud={isCloud} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} />;
case 'sso':
return <SSOTab isEditOrgTab={true} globalUrl={globalUrl} isCloud={isCloud} userdata={userdata} handleEditOrg={handleEditOrg} selectedOrganization={selectedOrganization}/>
case `notifications`:
case `priorities`:
return (
<Priorities
isCloud={isCloud}
userdata={userdata}
globalUrl={globalUrl}
checkLogin={checkLogin}
notifications={notifications}
setNotifications={setNotifications}
clickedFromOrgTab={true}
serverside={serverside}
isLoaded={isLoaded}
selectedOrganization={selectedOrganization}
handleEditOrg={handleEditOrg}
/>
);
case 'billingstats' :
case 'billing' :
return (
<Billing
isCloud={true}
userdata={userdata}
setSelectedOrganization={setSelectedOrganization}
globalUrl={globalUrl}
selectedOrganization={selectedOrganization}
billingInfo={billingInfo}
stripeKey={stripeKey}
handleGetOrg={handleGetOrg}
clickedFromOrgTab={true}
handleEditOrg={handleEditOrg}
removeCookie={removeCookie}
isLoaded={isLoaded}
/>
);
case 'branding(beta)':
return <Branding
isCloud={isCloud}
userdata={userdata}
globalUrl={globalUrl}
handleGetOrg={handleGetOrg}
selectedOrganization={selectedOrganization}
clickedFromOrgTab={true}
setSelectedOrganization={setSelectedOrganization}
/>;
// case 'analytics':
// return <AnalyticsTab isCloud={isCloud} userdata={userdata} globalUrl={globalUrl} />;
default:
return <EditOrgTab isCloud={isCloud} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} />;
}
};
return (
<div style={{ height: "100%", width: "100%", color: '#FFFFFF', backgroundColor: '#212121', borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", boxSizing: 'border-box' }}>
<div style={{ display: 'flex', justifyContent: 'space-around', width: "100%", borderBottom: '1px solid #494949' ,boxSizing: 'border-box' }}>
{['Org Configuration', "sso", "Notifications", 'Billing & Stats', 'Branding (Beta)'].map((tabName, index) => (
<Tooltip
key={index}
title={
((tabName === "sso")) && !(userdata?.support || userdata?.active_org?.role === "admin")
? "Your role is not admin. Please ask the admin to change your role."
: ""
}
placement="right"
>
<div style={{ pointerEvents: 'auto', width: '100%',}}>
<Button
key={tabName}
onClick={() => {
console.log("index", index);
setCurIndex(index);
handleTabClick(index === 0 ? "org_config" : tabName.toLowerCase().replace(/[\s&]+/g, ''));
}}
variant="text"
sx={{
"&.MuiButton-root": {
padding: '28px 0',
borderBottom: index === curIndex ? '2px solid #FF8444' : 'none',
cursor: 'pointer',
fontWeight: index === curIndex ? 'bold' : 'normal',
color: index === curIndex ? "#FF8444" : "#FFFFFF",
textTransform: 'none',
fontSize: 16,
width: "100%",
height: "100%",
borderRadius: 0,
},
"&: hover": {
backgroundColor: "#323232"
},
"&.Mui-disabled": {
color: "#6F6F6F",
},
}}
disabled={
((tabName === "sso")) && !(userdata?.support || userdata?.active_org?.role === "admin")
}
>
{index === 2 && unreadNotifications > 0 ? (
<div style={{ position: 'relative' }}>
<div style={{
position: 'absolute',
top: -12,
right: -15,
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: '#FF8444',
color: '#FFFFFF',
fontSize: 12,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}>
{unreadNotifications}
</div>
{tabName}
</div>
) : (
<>{index === 1 ? "SSO" : tabName}</>
)}
</Button>
</div>
</Tooltip>
))}
</div>
<div style={{ display: 'flex', justifyContent: 'center', width: "100%", height: "100%", boxSizing:'border-box'}}>
{renderContent()}
</div>
</div>
);
};
export default OrganizationTab;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+649
View File
@@ -0,0 +1,649 @@
import { useEffect } from "react";
import React from "react";
import {
Typography,
Switch,
Button,
Tooltip,
TextField,
Grid,
Skeleton,
Dialog,
DialogTitle,
DialogContent,
Box,
} from "@mui/material";
import { makeStyles } from "@mui/styles";
import { Link } from "react-router-dom";
import theme from "../theme.jsx";
import { toast } from "react-toastify";
const useStyles = makeStyles({
notchedOutline: {
borderColor: "#f85a3e !important",
},
});
const SSOTab = ({selectedOrganization, userdata, isEditOrgTab, globalUrl, handleEditOrg})=>{
const classes = useStyles();
const [show2faSetup, setShow2faSetup] = React.useState(false);
const [autoPrivision, setAutoProvision] = React.useState(selectedOrganization?.sso_config?.auto_provision)
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 [SSORequired, setSSORequired] = React.useState(selectedOrganization.sso_config === undefined
? false
: selectedOrganization.sso_config.SSORequired === undefined
? false
: selectedOrganization.sso_config.SSORequired);
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 [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
)
useEffect(()=>{
if (openidClientSecret !== selectedOrganization?.sso_config?.client_secret) {
setOpenidClientSecret(selectedOrganization?.sso_config?.client_secret)
}
if (openidClientId !== selectedOrganization?.sso_config?.client_id) {
setOpenidClientId(selectedOrganization?.sso_config?.client_id)
}
if (openidAuthorization !== selectedOrganization?.sso_config?.openid_authorization) {
setOpenidAuthorization(selectedOrganization?.sso_config?.openid_authorization)
}
if (openidToken !== selectedOrganization?.sso_config?.openid_token) {
setOpenidToken(selectedOrganization?.sso_config?.openid_token)
}
if (ssoCertificate !== selectedOrganization?.sso_config?.sso_certificate) {
setSsoCertificate(selectedOrganization?.sso_config?.sso_certificate)
}
if (ssoEntrypoint !== selectedOrganization?.sso_config?.sso_entrypoint) {
setSsoEntrypoint(selectedOrganization?.sso_config?.sso_entrypoint)
}
if (SSORequired !== selectedOrganization?.sso_config?.SSORequired) {
setSSORequired(selectedOrganization?.sso_config?.SSORequired)
}
if (autoPrivision !== selectedOrganization?.sso_config?.auto_provision) {
setAutoProvision(selectedOrganization?.sso_config?.auto_provision)
}
},[selectedOrganization])
const orgSaveButton = (
<Tooltip title="Save any unsaved data" placement="bottom">
<Button
style={{ width: 244, height: 51, flex: 1, textTransform: 'capitalize', padding: "16px, 24px, 16px, 24px", borderRadius: 4, backgroundColor: "#ff8544", color: "#1a1a1a", fontSize: 16, }}
variant="contained"
color="primary"
disabled={
userdata === undefined ||
userdata === null ||
userdata.admin !== "true"
}
onClick={() =>
handleEditOrg(
selectedOrganization?.name,
selectedOrganization?.description,
selectedOrganization.id,
selectedOrganization.image,
{
app_download_repo: selectedOrganization?.defaults?.app_download_repo,
app_download_branch: selectedOrganization?.defaults?.app_download_branch,
workflow_download_repo: selectedOrganization?.defaults?.workflow_download_repo,
workflow_download_branch: selectedOrganization?.defaults?.workflow_download_branch,
notification_workflow: selectedOrganization?.defaults?.notification_workflow,
documentation_reference: selectedOrganization?.defaults?.documentation_reference,
workflow_upload_repo: selectedOrganization?.defaults?.workflow_upload_repo,
workflow_upload_branch: selectedOrganization?.defaults?.workflow_upload_branch,
workflow_upload_username: selectedOrganization?.defaults?.workflow_upload_username,
workflow_upload_token: selectedOrganization?.defaults?.workflow_upload_token,
newsletter: !selectedOrganization?.defaults?.newsletter,
weekly_recommendations: selectedOrganization?.defaults?.weekly_recommendations,
},
{
sso_entrypoint: ssoEntrypoint,
sso_certificate: ssoCertificate,
client_id: openidClientId,
client_secret: openidClientSecret,
openid_authorization: openidAuthorization,
openid_token: openidToken,
SSORequired: SSORequired,
auto_provision: autoPrivision,
}
)
}
>
Save Changes
{/* <SaveIcon /> */}
</Button>
</Tooltip>
);
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 handleChangeAutoProvision = (event) => {
if (
ssoEntrypoint === "" &&
openidAuthorization === "" &&
openidToken === ""
) {
if (!autoPrivision) {
toast.error(
"Please fill in fields for either OpenID connect or SSO before continuing. "
);
return;
}
} else {
setAutoProvision((prev)=> !prev);
toast.info("Toggled Auto Provisioning. Remember to save.");
}
};
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 persists.",
{ duration: 3000 }
);
return null;
}
return response.json();
})
.then((responjson) => {
if (!responjson) return;
if (responjson["reason"] === "SSO_REDIRECT") {
toast.info(
"Redirecting to SSO login page as SSO is required for this organization.",
{
duration: 3000,
onClose: () => {
window.location.href = responjson["url"];
}
}
);
} else {
toast.error(
"No SSO found for this org. Please set up SSO for this org.",
{ duration: 3000 }
);
}
})
.catch((error) => {
console.error("Error for SSO test:", error);
toast.error(
"An error occurred while testing SSO. Please try again.",
{ duration: 3000 }
);
});
};
return (
<div style={{ width: "100%", height: "100%",boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: '#212121', borderRadius: '16px', }}>
<div style={{ height: "100%", width: "100%", overflowX: 'hidden', scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}} >
<div style={{ width: "100%", overflowX: 'hidden', maxWidth: 883}}>
<Typography style={{ width: "100%", fontWeight: 'bold', fontSize: 24}}>
SSO Configuration
</Typography>
<div
style={{
display: "flex",
flexDirection: "column",
width: "100%",
justifyContent: 'flex-start',
marginTop: 20
}}
>
<Typography
variant="body2"
color="textSecondary"
style={{ marginTop: 5, marginBottom: 5, color: "rgba(158, 158, 158, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400 }}
>
Make SAML SSO or OpenID Authentication Required or Optional for Your Organization.
</Typography>
<div>
<Switch
checked={SSORequired}
onChange={toggleBetweenRequiredOrOptional}
sx={{marginBottom: 0.6, marginTop: 0.6}}
name="onOffSwitch"
color="primary"
title="Make SAML SSO or OpenID Authentication Required or Optional for Your Organization"
/>
{SSORequired ? "Required" : "Optional"}
</div>
</div>
{/* auto privisiong in sso */}
<div
style={{
display: "flex",
flexDirection: "column",
width: "100%",
justifyContent: 'flex-start',
marginTop: 30
}}
>
<Typography
variant="body2"
color="textSecondary"
style={{ marginTop: 5, marginBottom: 5, color: "rgba(158, 158, 158, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400 }}
>
Auto-provisioning of users in SSO. By default, users are auto-provisioned in SSO when they login. If you enable this, no new user will be added in your organization when they login via SSO.
</Typography>
<div>
<Switch
checked={autoPrivision}
onChange={handleChangeAutoProvision}
sx={{marginBottom: 0.6, marginTop: 0.6}}
name="onOffSwitch"
color="primary"
title="Disable auto-provisioning of users in SSO"
/>
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
marginTop: 30,
width: "100%",
paddingBottom: 10,
}}
>
<Typography style={{color: "rgba(158, 158, 158, 1)", margin: "5px 0px 5px 0px", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }}>
You can test your SSO configuration by clicking the button below.
Before testing, ensure you have set Open ID Connect or SAML SSO
credentials.
</Typography>
<Tooltip
title={
!(
ssoEntrypoint?.length > 0 ||
ssoCertificate?.length > 0 ||
openidAuthorization?.length > 0 ||
openidClientId?.length > 0
)
? "Please ensure all SSO credentials are set before testing."
: ""
}
>
<span style={{ width: 100 }}>
<Button
variant="outlined"
color="primary"
style={{ width: 100, textTransform: "none", margin: "10px 10px 10px 0px" }}
disabled={
!(
ssoEntrypoint?.length > 0 ||
ssoCertificate?.length > 0 ||
openidAuthorization?.length > 0 ||
openidClientId?.length > 0
)
}
onClick={HandleTestSSO}
>
Test SSO
</Button>
</span>
</Tooltip>
</div>
<Grid item xs={12} sx={{marginTop: 2}}>
<span style={{ display: "flex", flexDirection: "column" }}>
<Typography style={{ textAlign: "left", color: "rgba(241, 241, 241, 1)", fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontSize: 24, fontWeight: "bold", }}>OpenID connect</Typography>
<span style={{ marginTop: 8, color: "rgba(158, 158, 158, 1)", fontSize: 16, fontWeight: 400 }}>
Configure and Authorize SAML / SSO or OpenID connect. {" "}
<a
target="_blank"
href="/docs/extensions#single-signon"
style={{ color: "rgba(255, 132, 68, 1)" }}
>
Learn more
</a>
</span>
</span>
<Typography style={{ textAlign: "left", fontSize: 16, marginTop: 8, color: "rgba(158, 158, 158, 1)", fontWeight: 400 }}>
IdP URL for Shuffle OpenID: <Link to={`${globalUrl}/api/v1/login_openid`} target="_blank" style={{ color: "rgba(241, 241, 241, 1)", textDecoration: "none", fontSize: 16,}}>{`${globalUrl}/api/v1/login_openid`}</Link>
</Typography>
<Grid container style={{ marginTop: 8, }} spacing={2}>
<Grid item xs={6} style={{}}>
<span>
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>Client ID</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
}}
fullWidth={true}
type="name"
multiline={true}
rows={2}
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: isEditOrgTab ? null : classes.notchedOutline,
},
style: {
color: "white",
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
},
}}
/>
</span>
</Grid>
<Grid item xs={6} style={{}}>
<span>
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>Client Secret</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
}}
fullWidth={true}
type="name"
multiline={true}
rows={2}
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: isEditOrgTab ? null : classes.notchedOutline,
},
style: {
color: "white",
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
},
}}
/>
</span>
</Grid>
</Grid>
<Grid container style={{ marginTop: 10, }} spacing={2}>
<Grid item xs={6} style={{}}>
<span>
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>Authorization URL</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
multiline={true}
rows={2}
placeholder="The OpenID authorization URL (usually ends with /authorize)"
value={openidAuthorization}
onChange={(e) => {
setOpenidAuthorization(e.target.value)
}}
InputProps={{
classes: {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
},
style: {
color: "white",
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
},
}}
/>
</span>
</Grid>
<Grid item xs={6} style={{}}>
<span>
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>Token URL</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
multiline={true}
rows={2}
placeholder="The OpenID token URL (usually ends with /token)"
value={openidToken}
onChange={(e) => {
setOpenidToken(e.target.value)
}}
InputProps={{
classes: {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
},
style: {
color: "white",
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
},
}}
/>
</span>
</Grid>
</Grid>
</Grid>
{/**/}
{/*isCloud ? null : */}
<Grid item xs={12} sx={{ marginTop: 3.5 }} >
<Typography variant="h4" style={{ textAlign: "left", color: "rgba(241, 241, 241, 1)", fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontSize: 24, fontWeight: 600, }}>SAML SSO (v1.1)</Typography>
<Typography variant="body2" style={{ textAlign: "left", marginTop: 8, color: "rgba(241, 241, 241, 1)", fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontSize: 16, fontWeight: 400, color: "rgba(158, 158, 158, 1)" }} color="textSecondary">
IdP URL for Shuffle SAML/SSO: <Link to={`${globalUrl}/api/v1/login_sso`} target="_blank" style={{ color: "rgba(241, 241, 241, 1)", textDecoration: "none" }}>{`${globalUrl}/api/v1/login_sso`}</Link>
</Typography>
<Grid container style={{ marginTop: 10, }} spacing={2}>
<Grid item xs={6} style={{}}>
<span>
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>SSO Entrypoint (IdP)</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
}}
fullWidth={true}
type="name"
multiline={true}
rows={2}
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: isEditOrgTab ? null : classes.notchedOutline,
},
style: {
color: "white",
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
},
}}
/>
</span>
</Grid>
<Grid item xs={6} style={{}}>
<span>
<Typography style={{ color: "rgba(255, 255, 255, 1)", fontSize: 16, fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", fontWeight: 400, fontSize: 16 }}>SSO Certificate (X509)</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
multiline={true}
rows={2}
placeholder="The X509 certificate to use"
value={ssoCertificate}
onChange={(e) => {
setSsoCertificate(e.target.value);
}}
InputProps={{
classes: {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
},
style: {
color: "white",
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
},
}}
/>
</span>
</Grid>
</Grid>
</Grid>
<div style={{ textAlign: "center", margin: "50px auto 0px auto", }}>
{orgSaveButton}
</div>
</div>
</div>
</div>
)
}
export default SSOTab
+1 -1
View File
@@ -318,7 +318,7 @@ const Admin2 = (props) => {
return (
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 29, zoom: 0.9}}>
<AdminNavBar userdata={userdata} isLoaded={isLoaded} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} selectedTab={selectedTab} orgId={selectedOrganization.id} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} setNotifications={setNotifications} stripeKey={stripeKey} notifications={notifications} checkLogin={checkLogin} globalUrl={globalUrl} isCloud={isCloud} serverside={serverside} />
<AdminNavBar userdata={userdata} isLoaded={isLoaded} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} selectedTab={selectedTab} orgId={selectedOrganization.id} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} setNotifications={setNotifications} stripeKey={stripeKey} notifications={notifications} checkLogin={checkLogin} globalUrl={globalUrl} isCloud={isCloud}/>
</div>
);
};
+1 -1
View File
@@ -19116,7 +19116,7 @@ const AngularWorkflow = (defaultprops) => {
const defaultImage =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg=="
const size = 40;
const size = isCloud ? 40 : 35;
const borderRadius = 5
if (execution.execution_source === undefined || execution.execution_source === null || execution.execution_source.length === 0) {
return (
+1 -1
View File
@@ -6243,7 +6243,7 @@ const AppCreator = (defaultprops) => {
{testView}
*/}
<div style={{height: 50, padding: 15, display: "flex", marginTop: 35, position: "fixed", bottom: 0, left: 0, width: "100%", backgroundColor: theme.palette?.backgroundColor, borderTop: "1px solid rgba(255,255,255,0.3)",}}>
<div style={{height: isCloud ? 50 : 80, padding: 15, display: "flex", marginTop: 35, position: "fixed", bottom: 0, left: 0, width: "100%", backgroundColor: theme.palette?.backgroundColor, borderTop: "1px solid rgba(255,255,255,0.3)",}}>
<div style={{width: 450, margin: "auto", display: "flex", textAlign: "center", }}>
{appDownloadData.length > 0 ?
<Tooltip title="Download the OpenAPI specification for the App" placement="bottom">
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -1221,8 +1221,7 @@ const Usecases2 = (props) => {
) : null
const data =
<div className="content" style={{width: isMobile ? "100%": leftSideBarOpenByClick ? 1000: 1200, margin: "auto", paddingBottom: 200, textAlign: "center", paddingLeft: leftSideBarOpenByClick ? 200 : 0, transition: "padding-left 0.3s ease, width 0.3s ease"}}>
<div className="content" style={{width: isMobile ? "100%": leftSideBarOpenByClick ? 1000: 1200, margin: "auto", paddingBottom: 200, textAlign: "center", paddingLeft: leftSideBarOpenByClick ? 50 : 0, transition: "padding-left 0.3s ease, width 0.3s ease"}}>
<UsecaseListComponent
userdata={userdata}
isLoggedIn={isLoggedIn}