sync onprem features/pages with cloud

This commit is contained in:
lalitdeore
2025-02-06 15:39:19 +05:30
parent f9e4a86245
commit 840347844a
30 changed files with 17680 additions and 72 deletions
+36 -3
View File
@@ -1048,6 +1048,20 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
licensed := shuffle.IsLicensed(ctx, *org) 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{ returnValue := shuffle.HandleInfo{
Success: true, Success: true,
Username: userInfo.Username, Username: userInfo.Username,
@@ -1069,6 +1083,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
Interests: orgInterests, Interests: orgInterests,
Priorities: orgPriorities, Priorities: orgPriorities,
Licensed: licensed, Licensed: licensed,
ActiveApps: activatedAppIds,
} }
returnData, err := json.Marshal(returnValue) 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) err = shuffle.DeployAppToDatastore(ctx, api)
//func DeployAppToDatastore(ctx context.Context, workflowapp WorkflowApp, bucketName string) error { //func DeployAppToDatastore(ctx context.Context, workflowapp WorkflowApp, bucketName string) error {
if err != nil { 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.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed adding app to db: %s"}`, err))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed adding app to db: %s"}`, err)))
return return
@@ -3184,7 +3199,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
// 2. Get all the required code // 2. Get all the required code
appbase, staticBaseline, err := shuffle.GetAppbase() appbase, staticBaseline, err := shuffle.GetAppbase()
if err != nil { if err != nil {
log.Printf("Failed getting appbase: %s", err) log.Printf("[ERROR] Failed getting appbase: %s", err)
resp.WriteHeader(500) resp.WriteHeader(500)
resp.Write([]byte(`{"success": false, "reason": "Failed getting appbase code"}`)) resp.Write([]byte(`{"success": false, "reason": "Failed getting appbase code"}`))
return return
@@ -4842,7 +4857,7 @@ func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) {
// FIXME - add org check too, and not just owner // FIXME - add org check too, and not just owner
// Check workflow.Sharing == private / public / org too // Check workflow.Sharing == private / public / org too
if user.Id != workflow.Owner || len(user.Id) == 0 { 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) log.Printf("[AUDIT] User %s is accessing workflow %s as admin (public)", user.Username, workflow.ID)
} else { } else {
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (public)", user.Username, workflow.ID) 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}/config", shuffle.SetAuthenticationConfig).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/authentication/{appauthId}", shuffle.DeleteAppAuthentication).Methods("DELETE", "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. // 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/{key}", shuffle.HandleGetUsecase).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/workflows/usecases", shuffle.LoadUsecases).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/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).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/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/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/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", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS")
//r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS") //r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "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. // 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. // 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}/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}/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}/validate_app_values", shuffle.HandleKeyValueCheck).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "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}/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}/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/{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.HandleGetStatistics).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleAppendStatistics).Methods("POST", "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}/statistics", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleListCacheKeys).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.HandleGetFileMeta).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "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", 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 // This structure is horrendous. Needs fixing after we got the prototype up
r.HandleFunc("/api/v1/detections/{detectionType}/connect", shuffle.HandleDetectionAutoConnect).Methods("GET", "OPTIONS") 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 { CookiesProvider } from "react-cookie";
import { removeCookies, useCookies } from "react-cookie"; import { removeCookies, useCookies } from "react-cookie";
import Workflows from "./views/Workflows"; import Workflows from "./views/Workflows.jsx";
import GettingStarted from "./views/GettingStarted"; import GettingStarted from "./views/GettingStarted.jsx";
import AngularWorkflow from "./views/AngularWorkflow.jsx"; import AngularWorkflow from "./views/AngularWorkflow.jsx";
import Header from "./components/NewHeader.jsx"; import Header from "./components/NewHeader.jsx";
import HealthPage from "./components/HealthPage.jsx"; import HealthPage from "./components/HealthPage.jsx";
//import Header from "./components/Header.jsx"; //import Header from "./components/Header.jsx";
import theme from "./theme"; import theme from "./theme.jsx";
import Apps from "./views/Apps"; import Apps from "./views/Apps.jsx";
import Apps2 from "./views/Apps2.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 DetectionDashBoard from "./views/DetectionDashboard.jsx";
import Welcome from "./views/Welcome.jsx"; import Welcome from "./views/Welcome.jsx";
import Dashboard from "./views/Dashboard.jsx"; import Dashboard from "./views/Dashboard.jsx";
import DashboardView from "./views/DashboardViews.jsx"; import DashboardView from "./views/DashboardViews.jsx";
import AdminSetup from "./views/AdminSetup"; import AdminSetup from "./views/AdminSetup.jsx";
import Admin from "./views/Admin"; import Admin from "./views/Admin.jsx";
import Docs from "./views/Docs.jsx"; import Docs from "./views/Docs.jsx";
import Usecases2 from "./views/Usecases2.jsx"; import Usecases2 from "./views/Usecases2.jsx";
//import Introduction from "./views/Introduction"; //import Introduction from "./views/Introduction";
import SetAuthentication from "./views/SetAuthentication"; import SetAuthentication from "./views/SetAuthentication.jsx";
import SetAuthenticationSSO from "./views/SetAuthenticationSSO"; import SetAuthenticationSSO from "./views/SetAuthenticationSSO.jsx";
import Search from "./views/Search.jsx"; import Search from "./views/Search.jsx";
import RunWorkflow from "./views/RunWorkflow.jsx"; import RunWorkflow from "./views/RunWorkflow.jsx";
import Admin2 from "./views/Admin2.jsx";
import LoginPage from "./views/LoginPage"; import LoginPage from "./views/LoginPage.jsx";
import SettingsPage from "./views/SettingsPage"; import SettingsPage from "./views/SettingsPage.jsx";
import KeepAlive from "./views/KeepAlive.jsx"; import KeepAlive from "./views/KeepAlive.jsx";
import { ThemeProvider } from "@mui/material/styles"; import { ThemeProvider } from "@mui/material/styles";
@@ -40,8 +41,8 @@ import CssBaseline from '@mui/material/CssBaseline';
import UpdateAuthentication from "./views/UpdateAuthentication.jsx"; import UpdateAuthentication from "./views/UpdateAuthentication.jsx";
import FrameworkWrapper from "./views/FrameworkWrapper.jsx"; import FrameworkWrapper from "./views/FrameworkWrapper.jsx";
import ScrollToTop from "./components/ScrollToTop"; import ScrollToTop from "./components/ScrollToTop.jsx";
import AlertTemplate from "./components/AlertTemplate"; import AlertTemplate from "./components/AlertTemplate.js";
import { isMobile } from "react-device-detect"; import { isMobile } from "react-device-detect";
import RuntimeDebugger from "./components/RuntimeDebugger.jsx" import RuntimeDebugger from "./components/RuntimeDebugger.jsx"
@@ -58,6 +59,7 @@ import Drift from "react-driftjs";
import { AppContext } from './context/ContextApi.jsx'; import { AppContext } from './context/ContextApi.jsx';
import Workflows2 from "./views/Workflows2.jsx"; import Workflows2 from "./views/Workflows2.jsx";
import AppExplorer from "./views/AppExplorer.jsx";
// Production - backend proxy forwarding in nginx // Production - backend proxy forwarding in nginx
var globalUrl = window.location.origin; var globalUrl = window.location.origin;
@@ -207,37 +209,27 @@ const App = (message, props) => {
} }
{curpath.includes("/workflows") && curpath.includes("/run") ? { window?.location?.pathname === "/" || window?.location?.pathname === "/training" || !(isLoggedIn && isLoaded) ? (
<div style={{ height: 60, }} /> <div style={{ minHeight: 68, maxHeight: 68 }}>
: <Header
isLoggedIn ? notifications={notifications}
<div style={{ position: 'fixed', top: 16, left: 10, zIndex: 100000 }}> setNotifications={setNotifications}
<LeftSideBar userdata={userdata} globalUrl={globalUrl} serverside={false} notifications={notifications} /> userdata={userdata}
cookies={cookies}
removeCookie={removeCookie}
isLoaded={isLoaded}
globalUrl={globalUrl}
setIsLoggedIn={setIsLoggedIn}
isLoggedIn={isLoggedIn}
curpath={curpath}
{...props}
/>
</div> </div>
: ) : (
<div style={{ minHeight: 68, maxHeight: 68, }}> <div style={{ position: 'fixed', top: 32, left: 10, zIndex: 100000 }}>
<Header <LeftSideBar checkLogin={checkLogin} userdata={userdata} globalUrl={globalUrl} notifications={notifications} />
billingInfo={{}} </div>
) }
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={{ height: 60 }} /> <div style={{ height: 60 }} />
@@ -262,7 +254,7 @@ const App = (message, props) => {
/> />
<Route <Route
exact exact
path="/admin" path="/admin2"
element={ element={
<Admin <Admin
userdata={userdata} 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 path="/search" element={<Search serverside={false} isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} {...props} /> } />
<Route <Route
exact exact
@@ -387,7 +397,7 @@ const App = (message, props) => {
/> />
<Route <Route
exact exact
path="/usecases" path="/usecases2"
element={ element={
<Dashboard <Dashboard
userdata={userdata} userdata={userdata}
@@ -400,7 +410,7 @@ const App = (message, props) => {
/> />
<Route <Route
exact exact
path="/usecases2" path="/usecases"
element={ element={
<Usecases2 <Usecases2
userdata={userdata} 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/authentication" element={<UpdateAuthentication serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
<Route <Route
exact exact
path="/apps" path="/apps2"
element={ element={
<Apps <Apps
isLoaded={isLoaded} isLoaded={isLoaded}
@@ -439,7 +449,7 @@ const App = (message, props) => {
/> />
<Route <Route
exact exact
path="/apps2" path="/apps"
element={ element={
<Apps2 <Apps2
serverside={false} 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 <Route
exact exact
path="/detections/sigma" path="/detections/sigma"
@@ -474,7 +485,7 @@ const App = (message, props) => {
/> />
<Route <Route
exact exact
path="/workflows" path="/workflows2"
element={ element={
<Workflows <Workflows
checkLogin={checkLogin} checkLogin={checkLogin}
@@ -491,7 +502,7 @@ const App = (message, props) => {
/> />
<Route <Route
exact exact
path="/workflows2" path="/workflows"
element={ element={
<Workflows2 <Workflows2
checkLogin={checkLogin} checkLogin={checkLogin}
@@ -554,6 +565,7 @@ const App = (message, props) => {
isMobile={isMobile} isMobile={isMobile}
isLoaded={isLoaded} isLoaded={isLoaded}
globalUrl={globalUrl} globalUrl={globalUrl}
isLoggedIn={isLoggedIn}
{...props} {...props}
/> />
} }
@@ -567,6 +579,7 @@ const App = (message, props) => {
isMobile={isMobile} isMobile={isMobile}
isLoaded={isLoaded} isLoaded={isLoaded}
globalUrl={globalUrl} globalUrl={globalUrl}
isLoggedIn={isLoggedIn}
{...props} {...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) => { {hits.map((data, index) => {
const appUrl = const appUrl =
isCloud isCloud === true ?
? `/apps/${data.objectID}?queryID=${data.__queryID}` `/apps/${data.id}`
: `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`; : `https://shuffler.io/apps/${data.objectID}`;
if (data.name === "" && data.id === "") {
return null
}
return ( return (
<Zoom <Zoom
@@ -1732,13 +1736,13 @@ const AppGrid = (props) => {
}; };
const appUrl = const appUrl =
isCloud === true isCloud === true ?
? `/apps/${data.id}` `/apps/${data.id}`
: `https://shuffler.io/apps/${data.id}`; : `https://shuffler.io/apps/${data.id}`;
if (data.name === "" && data.id === "") { if (data.name === "" && data.id === "") {
return null return null
} }
return ( return (
<Zoom <Zoom
+4 -2
View File
@@ -477,7 +477,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
const isCloud = const isCloud =
window.location.host === "localhost:3002" || window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io" || window.location.host === "localhost:3000" window.location.host === "shuffler.io"
? true ? true
: false; : false;
@@ -574,8 +574,10 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
{newAppname} {newAppname}
</Typography> </Typography>
<Link <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" }} style={{ textDecoration: "none", color: "#f85a3e", marginTop: "-2px" }}
target="_blank"
rel="noopener noreferrer"
> >
<IconButton <IconButton
style={{ 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
+2 -2
View File
@@ -889,7 +889,7 @@ useEffect(() => {
</Button> </Button>
</Box> </Box>
</Box> </Box>
<Box sx={{ display: "flex", flexDirection: "column", width:"100%", height: "100%", overflowY: "auto", overflowX: "hidden",transition: 'display 0.3s ease',paddingTop: 0.5 }} onMouseOver={()=>{!leftSideBarOpenByClick && setExpandLeftNav(true)}} onMouseLeave={()=>{!leftSideBarOpenByClick && setExpandLeftNav(false);setOpenAutocomplete(false)}}> <Box sx={{ display: "flex", flexDirection: "column", width:"100%", height: "100%", overflowY: "auto", overflowX: "hidden",transition: 'display 0.3s ease',paddingTop: 0.5 }} onMouseOver={()=>{(!leftSideBarOpenByClick || window?.location?.pathname?.includes("/workflows/")) && setExpandLeftNav(true)}} onMouseLeave={()=>{(!leftSideBarOpenByClick || window?.location?.pathname?.includes("/workflows/")) && setExpandLeftNav(false);setOpenAutocomplete(false)}}>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@@ -1907,4 +1907,4 @@ useEffect(() => {
); );
}; };
export default LeftSideBar; export default LeftSideBar;
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
+11 -2
View File
@@ -12,6 +12,14 @@ const Admin2 = (props) => {
const [orgRequest, setOrgRequest] = React.useState(true); const [orgRequest, setOrgRequest] = React.useState(true);
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
if (document !== undefined) {
if (selectedOrganization?.name !== undefined) {
document.title = selectedOrganization?.name + " - Admin - Shuffle"
} else {
document.title = "Admin - Shuffle"
}
}
const handleGetOrg = (orgId) => { const handleGetOrg = (orgId) => {
fetch(`${globalUrl}/api/v1/orgs/${orgId}`, { fetch(`${globalUrl}/api/v1/orgs/${orgId}`, {
@@ -103,7 +111,8 @@ const Admin2 = (props) => {
setSelectedStatus(leads); setSelectedStatus(leads);
} }
setSelectedOrganization(responseJson);
setSelectedOrganization(responseJson)
var lists = { var lists = {
active: { active: {
triggers: [], triggers: [],
@@ -309,7 +318,7 @@ const Admin2 = (props) => {
return ( return (
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 29, zoom: 0.9}}> <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> </div>
); );
}; };
+1 -1
View File
@@ -18484,7 +18484,7 @@ const AngularWorkflow = (defaultprops) => {
const defaultImage = 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==" "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 const borderRadius = 5
if (execution.execution_source === undefined || execution.execution_source === null || execution.execution_source.length === 0) { if (execution.execution_source === undefined || execution.execution_source === null || execution.execution_source.length === 0) {
return ( return (
+1 -1
View File
@@ -6204,7 +6204,7 @@ const AppCreator = (defaultprops) => {
{testView} {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", }}> <div style={{width: 450, margin: "auto", display: "flex", textAlign: "center", }}>
{appDownloadData.length > 0 ? {appDownloadData.length > 0 ?
<Tooltip title="Download the OpenAPI specification for the App" placement="bottom"> <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 ) : null
const data = 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 <UsecaseListComponent
userdata={userdata} userdata={userdata}
isLoggedIn={isLoggedIn} isLoggedIn={isLoggedIn}