Merge pull request #1619 from LalitDeore/test
sync onprem features/pages with cloud
@@ -1048,6 +1048,20 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
||||
|
||||
licensed := shuffle.IsLicensed(ctx, *org)
|
||||
|
||||
workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 1000, 0)
|
||||
if err != nil {
|
||||
log.Printf("{WARNING] Failed getting apps (getworkflowapps): %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
orgApps := workflowapps
|
||||
activatedAppIds := []string{}
|
||||
for _, app := range orgApps {
|
||||
activatedAppIds = append(activatedAppIds, app.ID)
|
||||
}
|
||||
|
||||
returnValue := shuffle.HandleInfo{
|
||||
Success: true,
|
||||
Username: userInfo.Username,
|
||||
@@ -1069,6 +1083,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
||||
Interests: orgInterests,
|
||||
Priorities: orgPriorities,
|
||||
Licensed: licensed,
|
||||
ActiveApps: activatedAppIds,
|
||||
}
|
||||
|
||||
returnData, err := json.Marshal(returnValue)
|
||||
@@ -3175,7 +3190,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
|
||||
err = shuffle.DeployAppToDatastore(ctx, api)
|
||||
//func DeployAppToDatastore(ctx context.Context, workflowapp WorkflowApp, bucketName string) error {
|
||||
if err != nil {
|
||||
log.Printf("Failed adding app to db: %s", err)
|
||||
log.Printf("[ERROR] Failed adding app to db: %s", err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed adding app to db: %s"}`, err)))
|
||||
return
|
||||
@@ -3184,7 +3199,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s
|
||||
// 2. Get all the required code
|
||||
appbase, staticBaseline, err := shuffle.GetAppbase()
|
||||
if err != nil {
|
||||
log.Printf("Failed getting appbase: %s", err)
|
||||
log.Printf("[ERROR] Failed getting appbase: %s", err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Failed getting appbase code"}`))
|
||||
return
|
||||
@@ -4842,7 +4857,7 @@ func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) {
|
||||
// FIXME - add org check too, and not just owner
|
||||
// Check workflow.Sharing == private / public / org too
|
||||
if user.Id != workflow.Owner || len(user.Id) == 0 {
|
||||
if workflow.OrgId == user.ActiveOrg.Id {
|
||||
if workflow.OrgId == user.ActiveOrg.Id {
|
||||
log.Printf("[AUDIT] User %s is accessing workflow %s as admin (public)", user.Username, workflow.ID)
|
||||
} else {
|
||||
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (public)", user.Username, workflow.ID)
|
||||
@@ -5107,6 +5122,13 @@ func initHandlers() {
|
||||
r.HandleFunc("/api/v1/apps/authentication/{appauthId}/config", shuffle.SetAuthenticationConfig).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/authentication/{appauthId}", shuffle.DeleteAppAuthentication).Methods("DELETE", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/authentication/group", shuffle.AddAppAuthenticationGroup).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/authentication/group", shuffle.GetAppAuthenticationGroup).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/authentication/group/{key}", shuffle.DeleteAppAuthenticationGroup).Methods("DELETE", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/authentication/groups", shuffle.AddAppAuthenticationGroup).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/authentication/groups", shuffle.GetAppAuthenticationGroup).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/authentication/groups/{key}", shuffle.DeleteAppAuthenticationGroup).Methods("DELETE", "OPTIONS")
|
||||
// Related to use-cases that are not directly workflows.
|
||||
r.HandleFunc("/api/v1/workflows/usecases/{key}", shuffle.HandleGetUsecase).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/workflows/usecases", shuffle.LoadUsecases).Methods("GET", "OPTIONS")
|
||||
@@ -5173,9 +5195,11 @@ func initHandlers() {
|
||||
r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/triggers/github/register", shuffle.HandleNewGithubRegister).Methods("PUT", "OPTIONS")
|
||||
//r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/pipelines/{key}", handlePipelineCallback).Methods("POST", "GET", "PATCH", "PUT", "DELETE", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/triggers", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS")
|
||||
|
||||
//r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS")
|
||||
@@ -5204,8 +5228,13 @@ func initHandlers() {
|
||||
|
||||
// This is a new API that validates if a key has been seen before.
|
||||
// Not sure what the best course of action is for it.
|
||||
r.HandleFunc("/api/v1/getenvironments", shuffle.HandleGetEnvironments).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/setenvironments", shuffle.HandleSetEnvironments).Methods("PUT", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments/{key}/stop", shuffle.HandleStopExecutions).Methods("GET", "POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments/{key}/rerun", shuffle.HandleRerunExecutions).Methods("GET", "POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments/{key}/stats", shuffle.HandleGetenvStats).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments/{key}/config", shuffle.HandleSetenvConfig).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments", shuffle.HandleGetEnvironments).Methods("GET", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/validate_app_values", shuffle.HandleKeyValueCheck).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
|
||||
@@ -5214,8 +5243,10 @@ func initHandlers() {
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/delete_cache", shuffle.HandleDeleteCacheKeyPost).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/cache/config", shuffle.HandleCacheConfig).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleAppendStatistics).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/stats/{key}", shuffle.GetSpecificStats).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/statistics", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
|
||||
@@ -5242,6 +5273,8 @@ func initHandlers() {
|
||||
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleGetFileMeta).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/files", shuffle.HandleGetFiles).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/files/{fileId}/config", shuffle.HandleSetFileConfig).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/files/namespaces/{namespace}/share", shuffle.HandleShareNamespace).Methods("POST", "OPTIONS")
|
||||
|
||||
// This structure is horrendous. Needs fixing after we got the prototype up
|
||||
r.HandleFunc("/api/v1/detections/{detectionType}/connect", shuffle.HandleDetectionAutoConnect).Methods("GET", "OPTIONS")
|
||||
|
||||
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 27 KiB |
@@ -4,35 +4,36 @@ import { Link, Route, Routes, BrowserRouter, useNavigate } from "react-router-do
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { removeCookies, useCookies } from "react-cookie";
|
||||
|
||||
import Workflows from "./views/Workflows";
|
||||
import GettingStarted from "./views/GettingStarted";
|
||||
import Workflows from "./views/Workflows.jsx";
|
||||
import GettingStarted from "./views/GettingStarted.jsx";
|
||||
import AngularWorkflow from "./views/AngularWorkflow.jsx";
|
||||
|
||||
import Header from "./components/NewHeader.jsx";
|
||||
import HealthPage from "./components/HealthPage.jsx";
|
||||
|
||||
//import Header from "./components/Header.jsx";
|
||||
import theme from "./theme";
|
||||
import Apps from "./views/Apps";
|
||||
import theme from "./theme.jsx";
|
||||
import Apps from "./views/Apps.jsx";
|
||||
import Apps2 from "./views/Apps2.jsx";
|
||||
import AppCreator from "./views/AppCreator";
|
||||
import AppCreator from "./views/AppCreator.jsx";
|
||||
import DetectionDashBoard from "./views/DetectionDashboard.jsx";
|
||||
|
||||
import Welcome from "./views/Welcome.jsx";
|
||||
import Dashboard from "./views/Dashboard.jsx";
|
||||
import DashboardView from "./views/DashboardViews.jsx";
|
||||
import AdminSetup from "./views/AdminSetup";
|
||||
import Admin from "./views/Admin";
|
||||
import AdminSetup from "./views/AdminSetup.jsx";
|
||||
import Admin from "./views/Admin.jsx";
|
||||
import Docs from "./views/Docs.jsx";
|
||||
import Usecases2 from "./views/Usecases2.jsx";
|
||||
//import Introduction from "./views/Introduction";
|
||||
import SetAuthentication from "./views/SetAuthentication";
|
||||
import SetAuthenticationSSO from "./views/SetAuthenticationSSO";
|
||||
import SetAuthentication from "./views/SetAuthentication.jsx";
|
||||
import SetAuthenticationSSO from "./views/SetAuthenticationSSO.jsx";
|
||||
import Search from "./views/Search.jsx";
|
||||
import RunWorkflow from "./views/RunWorkflow.jsx";
|
||||
import Admin2 from "./views/Admin2.jsx";
|
||||
|
||||
import LoginPage from "./views/LoginPage";
|
||||
import SettingsPage from "./views/SettingsPage";
|
||||
import LoginPage from "./views/LoginPage.jsx";
|
||||
import SettingsPage from "./views/SettingsPage.jsx";
|
||||
import KeepAlive from "./views/KeepAlive.jsx";
|
||||
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
@@ -40,8 +41,8 @@ import CssBaseline from '@mui/material/CssBaseline';
|
||||
|
||||
import UpdateAuthentication from "./views/UpdateAuthentication.jsx";
|
||||
import FrameworkWrapper from "./views/FrameworkWrapper.jsx";
|
||||
import ScrollToTop from "./components/ScrollToTop";
|
||||
import AlertTemplate from "./components/AlertTemplate";
|
||||
import ScrollToTop from "./components/ScrollToTop.jsx";
|
||||
import AlertTemplate from "./components/AlertTemplate.js";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import RuntimeDebugger from "./components/RuntimeDebugger.jsx"
|
||||
|
||||
@@ -58,6 +59,7 @@ import Drift from "react-driftjs";
|
||||
|
||||
import { AppContext } from './context/ContextApi.jsx';
|
||||
import Workflows2 from "./views/Workflows2.jsx";
|
||||
import AppExplorer from "./views/AppExplorer.jsx";
|
||||
|
||||
// Production - backend proxy forwarding in nginx
|
||||
var globalUrl = window.location.origin;
|
||||
@@ -207,37 +209,27 @@ const App = (message, props) => {
|
||||
}
|
||||
|
||||
|
||||
{curpath.includes("/workflows") && curpath.includes("/run") ?
|
||||
<div style={{ height: 60, }} />
|
||||
:
|
||||
isLoggedIn ?
|
||||
<div style={{ position: 'fixed', top: 16, left: 10, zIndex: 100000 }}>
|
||||
<LeftSideBar userdata={userdata} globalUrl={globalUrl} serverside={false} notifications={notifications} />
|
||||
{ window?.location?.pathname === "/" || window?.location?.pathname === "/training" || !(isLoggedIn && isLoaded) ? (
|
||||
<div style={{ minHeight: 68, maxHeight: 68 }}>
|
||||
<Header
|
||||
notifications={notifications}
|
||||
setNotifications={setNotifications}
|
||||
userdata={userdata}
|
||||
cookies={cookies}
|
||||
removeCookie={removeCookie}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
setIsLoggedIn={setIsLoggedIn}
|
||||
isLoggedIn={isLoggedIn}
|
||||
curpath={curpath}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
<div style={{ minHeight: 68, maxHeight: 68, }}>
|
||||
<Header
|
||||
billingInfo={{}}
|
||||
|
||||
notifications={notifications}
|
||||
setNotifications={setNotifications}
|
||||
checkLogin={checkLogin}
|
||||
cookies={cookies}
|
||||
removeCookie={removeCookie}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
setIsLoggedIn={setIsLoggedIn}
|
||||
isLoggedIn={isLoggedIn}
|
||||
userdata={userdata}
|
||||
|
||||
curpath={curpath}
|
||||
serverside={false}
|
||||
isMobile={false}
|
||||
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
) : (
|
||||
<div style={{ position: 'fixed', top: 32, left: 10, zIndex: 100000 }}>
|
||||
<LeftSideBar checkLogin={checkLogin} userdata={userdata} globalUrl={globalUrl} notifications={notifications} />
|
||||
</div>
|
||||
) }
|
||||
|
||||
{/*
|
||||
<div style={{ height: 60 }} />
|
||||
@@ -262,7 +254,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/admin"
|
||||
path="/admin2"
|
||||
element={
|
||||
<Admin
|
||||
userdata={userdata}
|
||||
@@ -279,6 +271,24 @@ const App = (message, props) => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/admin"
|
||||
element={
|
||||
<Admin2
|
||||
cookies={cookies}
|
||||
removeCookie={removeCookie}
|
||||
isLoaded={isLoaded}
|
||||
isLoggedIn={isLoggedIn}
|
||||
notifications={notifications}
|
||||
setNotifications={setNotifications}
|
||||
globalUrl={globalUrl}
|
||||
checkLogin={checkLogin}
|
||||
userdata={userdata}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route exact path="/search" element={<Search serverside={false} isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} {...props} /> } />
|
||||
<Route
|
||||
exact
|
||||
@@ -387,7 +397,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/usecases"
|
||||
path="/usecases2"
|
||||
element={
|
||||
<Dashboard
|
||||
userdata={userdata}
|
||||
@@ -400,7 +410,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/usecases2"
|
||||
path="/usecases"
|
||||
element={
|
||||
<Usecases2
|
||||
userdata={userdata}
|
||||
@@ -426,7 +436,7 @@ const App = (message, props) => {
|
||||
<Route exact path="/apps/authentication" element={<UpdateAuthentication serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
<Route
|
||||
exact
|
||||
path="/apps"
|
||||
path="/apps2"
|
||||
element={
|
||||
<Apps
|
||||
isLoaded={isLoaded}
|
||||
@@ -439,7 +449,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/apps2"
|
||||
path="/apps"
|
||||
element={
|
||||
<Apps2
|
||||
serverside={false}
|
||||
@@ -466,7 +476,8 @@ const App = (message, props) => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route exact path="/apis/:appid" element={<ApiExplorerWrapper serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} isMobile={false} selectedApp={undefined} isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} checkLogin={checkLogin} {...props} />} />
|
||||
{/* <Route exact path="/apps/:appid" element={<AppExplorer userdata={userdata} isLoggedIn={isLoggedIn} isLoaded={isLoaded} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} checkLogin={checkLogin} {...props} />} />
|
||||
<Route exact path="/apis/:appid" element={<ApiExplorerWrapper serverside={false} userdata={userdata} isLoggedIn={isLoggedIn} isMobile={false} selectedApp={undefined} isLoaded={isLoaded}globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor} checkLogin={checkLogin} {...props} />} /> */}
|
||||
<Route
|
||||
exact
|
||||
path="/detections/sigma"
|
||||
@@ -474,7 +485,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/workflows"
|
||||
path="/workflows2"
|
||||
element={
|
||||
<Workflows
|
||||
checkLogin={checkLogin}
|
||||
@@ -491,7 +502,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/workflows2"
|
||||
path="/workflows"
|
||||
element={
|
||||
<Workflows2
|
||||
checkLogin={checkLogin}
|
||||
@@ -554,6 +565,7 @@ const App = (message, props) => {
|
||||
isMobile={isMobile}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
isLoggedIn={isLoggedIn}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
@@ -567,6 +579,7 @@ const App = (message, props) => {
|
||||
isMobile={isMobile}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
isLoggedIn={isLoggedIn}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})
|
||||
@@ -416,9 +416,13 @@ const AppGrid = (props) => {
|
||||
>
|
||||
{hits.map((data, index) => {
|
||||
const appUrl =
|
||||
isCloud
|
||||
? `/apps/${data.objectID}?queryID=${data.__queryID}`
|
||||
: `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`;
|
||||
isCloud === true ?
|
||||
`/apps/${data.id}`
|
||||
: `https://shuffler.io/apps/${data.objectID}`;
|
||||
|
||||
if (data.name === "" && data.id === "") {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Zoom
|
||||
@@ -1732,13 +1736,13 @@ const AppGrid = (props) => {
|
||||
};
|
||||
|
||||
const appUrl =
|
||||
isCloud === true
|
||||
? `/apps/${data.id}`
|
||||
isCloud === true ?
|
||||
`/apps/${data.id}`
|
||||
: `https://shuffler.io/apps/${data.id}`;
|
||||
|
||||
if (data.name === "" && data.id === "") {
|
||||
return null
|
||||
}
|
||||
if (data.name === "" && data.id === "") {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Zoom
|
||||
|
||||
@@ -477,7 +477,7 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
|
||||
|
||||
const isCloud =
|
||||
window.location.host === "localhost:3002" ||
|
||||
window.location.host === "shuffler.io" || window.location.host === "localhost:3000"
|
||||
window.location.host === "shuffler.io"
|
||||
? true
|
||||
: false;
|
||||
|
||||
@@ -574,8 +574,10 @@ const AppModal = ({ open, onClose, app, globalUrl, getApps }) => {
|
||||
{newAppname}
|
||||
</Typography>
|
||||
<Link
|
||||
to={"/apps/" + (app?.id || app?.objectID)}
|
||||
to={isCloud ? "/apps/" + (app?.id || app?.objectID) : `https://shuffler.io/apps/${app?.objectID || app?.id}`}
|
||||
style={{ textDecoration: "none", color: "#f85a3e", marginTop: "-2px" }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<IconButton
|
||||
style={{
|
||||
|
||||
@@ -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;
|
||||
@@ -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: </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;
|
||||
@@ -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;
|
||||
@@ -1907,4 +1907,4 @@ useEffect(() => {
|
||||
);
|
||||
};
|
||||
|
||||
export default LeftSideBar;
|
||||
export default LeftSideBar;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -318,7 +318,7 @@ const Admin2 = (props) => {
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 29, zoom: 0.9}}>
|
||||
<AdminNavBar userdata={userdata} isLoaded={isLoaded} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} selectedTab={selectedTab} orgId={selectedOrganization.id} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} setNotifications={setNotifications} stripeKey={stripeKey} notifications={notifications} checkLogin={checkLogin} globalUrl={globalUrl} isCloud={isCloud} serverside={serverside} />
|
||||
<AdminNavBar userdata={userdata} isLoaded={isLoaded} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} selectedTab={selectedTab} orgId={selectedOrganization.id} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} setNotifications={setNotifications} stripeKey={stripeKey} notifications={notifications} checkLogin={checkLogin} globalUrl={globalUrl} isCloud={isCloud}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19116,7 +19116,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
const defaultImage =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg=="
|
||||
|
||||
const size = 40;
|
||||
const size = isCloud ? 40 : 35;
|
||||
const borderRadius = 5
|
||||
if (execution.execution_source === undefined || execution.execution_source === null || execution.execution_source.length === 0) {
|
||||
return (
|
||||
|
||||
@@ -6243,7 +6243,7 @@ const AppCreator = (defaultprops) => {
|
||||
{testView}
|
||||
*/}
|
||||
|
||||
<div style={{height: 50, padding: 15, display: "flex", marginTop: 35, position: "fixed", bottom: 0, left: 0, width: "100%", backgroundColor: theme.palette?.backgroundColor, borderTop: "1px solid rgba(255,255,255,0.3)",}}>
|
||||
<div style={{height: isCloud ? 50 : 80, padding: 15, display: "flex", marginTop: 35, position: "fixed", bottom: 0, left: 0, width: "100%", backgroundColor: theme.palette?.backgroundColor, borderTop: "1px solid rgba(255,255,255,0.3)",}}>
|
||||
<div style={{width: 450, margin: "auto", display: "flex", textAlign: "center", }}>
|
||||
{appDownloadData.length > 0 ?
|
||||
<Tooltip title="Download the OpenAPI specification for the App" placement="bottom">
|
||||
|
||||
@@ -1221,8 +1221,7 @@ const Usecases2 = (props) => {
|
||||
) : null
|
||||
|
||||
const data =
|
||||
<div className="content" style={{width: isMobile ? "100%": leftSideBarOpenByClick ? 1000: 1200, margin: "auto", paddingBottom: 200, textAlign: "center", paddingLeft: leftSideBarOpenByClick ? 200 : 0, transition: "padding-left 0.3s ease, width 0.3s ease"}}>
|
||||
|
||||
<div className="content" style={{width: isMobile ? "100%": leftSideBarOpenByClick ? 1000: 1200, margin: "auto", paddingBottom: 200, textAlign: "center", paddingLeft: leftSideBarOpenByClick ? 50 : 0, transition: "padding-left 0.3s ease, width 0.3s ease"}}>
|
||||
<UsecaseListComponent
|
||||
userdata={userdata}
|
||||
isLoggedIn={isLoggedIn}
|
||||
|
||||