Optimized Api Explorer and testing system

This commit is contained in:
Frikky
2025-02-17 12:59:56 +01:00
parent 6719f527e8
commit 440a6bcb23
11 changed files with 689 additions and 48 deletions
+275
View File
@@ -0,0 +1,275 @@
import React, { useEffect, useState } from 'react';
import Switch from '@mui/material/Switch';
import { Typography, Button } from '@mui/material';
import { useNavigate, Link, useParams } from "react-router-dom";
import { Bar } from 'react-chartjs-2';
import Grid from '@mui/material/Grid';
import SearchIcon from '@mui/icons-material/Search';
import NewReleasesIcon from '@mui/icons-material/NewReleases';
import MailOutlineIcon from '@mui/icons-material/MailOutline';
const AnalyticsTab = (props) => {
const { userdata, globalUrl, serverside } = props;
const [checked, setChecked] = useState(false);
const [selectedOption, setSelectedOption] = useState('all');
const [expand, setExpand] = useState(false)
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
const handleOptionChange = (option) => {
setSelectedOption(option);
// You can perform actions based on the selected option, such as filtering data
};
const handleChange = (event) => {
setChecked(event.target.checked);
};
const allData = {
labels: ['Email analysis', 'Email Management', 'EDR to Ticket', 'Ticket Analysis'],
datasets: [
{
label: 'Revisions',
data: [12, 19, 3, 5], // Data for revisions
backgroundColor: '#FF8444',
borderWidth: 1,
// borderRadius: 60,
barPercentage: 0.7,
categoryPercentage: 0.5,
},
{
label: 'Runs',
data: [8, 15, 5, 8], // Data for runs
backgroundColor: '#9747FF',
borderWidth: 1,
// borderRadius: 60,
barPercentage: 0.7,
categoryPercentage: 0.5
}
]
};
let data;
if (selectedOption === 'all') {
data = allData;
} else if (selectedOption === 'revisions') {
data = {
labels: allData.labels,
datasets: [allData.datasets[0]] // Show only revisions data
};
} else if (selectedOption === 'run') {
data = {
labels: allData.labels,
datasets: [allData.datasets[1]] // Show only runs data
};
}
// Options for the chart
const options = {
scales: {
yAxes: [
{
ticks: {
beginAtZero: true
}
}
]
},
};
return (
<div style={{ width: 1030, marginLeft: 20, marginTop:10, paddingRight: 17, paddingLeft: 17}}>
<div style={{ width: 985, display: 'flex', alignItems: 'start', paddingTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
<div style={{ marginLeft: 18 }}>Timeline</div>
</div>
<div style={{ display: "flex", width: "100%", marginTop: 16 }}>
<div>
<div style={{ width: 595, alignItems: 'start', paddingTop: 16, paddingBottom: checked ? 28 : 20, marginRight: 20, fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{ marginLeft: 18, marginRight: 310 }}>Apps</div>
<Switch checked={checked}
onChange={handleChange} /> Category
<div style={{ marginLeft: 16, borderLeft: "1px solid #D9D9D9", width: 10, height: 15 }} />
<Link onClick={() => { setExpand(prevExpand => !prevExpand); }} style={{ color: "#FF8444" }}>Expand</Link>
</div>
{expand ? null :
<div style={{ marginTop: 8, display: "flex" }}>
<div style={{ marginLeft: 19, }}>
<Typography style={{ fontSize: 13, color: "#9E9E9E", textAlign: "start" }}>Onboarding</Typography>
<Grid container spacing={2} style={{ marginTop: 1 }} >
<Grid item xs={4}>
<div style={{ position: 'relative' }}>
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
{checked ?
<div style={{
position: 'absolute',
bottom: 0,
left: '50%',
top: 30,
transform: 'translateX(-50%)',
backgroundColor: '#2f2f2f',
borderRadius: '50%',
height: 24,
width: 24,
transition: 'transform 0.5s ease',
}}><SearchIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
null
}
</div>
</Grid>
<Grid item xs={4}>
<div style={{ position: 'relative' }}>
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
{checked ?
<div style={{
position: 'absolute',
bottom: 0,
left: '50%',
top: 30,
transform: 'translateX(-50%)',
backgroundColor: '#2f2f2f',
borderRadius: '50%',
height: 24,
width: 24,
transition: 'transform 0.5s ease',
}}><MailOutlineIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
null
}
</div>
</Grid>
<Grid item xs={4}>
<div style={{ position: 'relative' }}>
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
{checked ?
<div style={{
position: 'absolute',
bottom: 0,
left: '50%',
top: 30,
transform: 'translateX(-50%)',
backgroundColor: '#2f2f2f',
borderRadius: '50%',
height: 24,
width: 24,
transition: 'transform 0.5s ease',
}}><NewReleasesIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
null
}
</div>
</Grid>
</Grid>
</div>
<div style={{ borderLeft: "1px solid #494949", justifyContent: "center", alignItems: "center", marginTop: 40, marginLeft: 20, marginRight: 20, height: 40 }}></div>
<div style={{}}>
<Typography style={{ fontSize: 13, color: "#9E9E9E", textAlign: "start" }}>Other</Typography>
<Grid container spacing={2} style={{ marginTop: 1 }} >
<Grid item xs={4}>
<div style={{ position: 'relative' }}>
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
{checked ?
<div style={{
position: 'absolute',
bottom: 0,
left: '50%',
top: 30,
transform: 'translateX(-50%)',
backgroundColor: '#2f2f2f',
borderRadius: '50%',
height: 24,
width: 24,
transition: 'transform 0.5s ease',
}}><SearchIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
null
}
</div>
</Grid>
<Grid item xs={4}>
<div style={{ position: 'relative' }}>
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
{checked ?
<div style={{
position: 'absolute',
bottom: 0,
left: '50%',
top: 30,
transform: 'translateX(-50%)',
backgroundColor: '#2f2f2f',
borderRadius: '50%',
height: 24,
width: 24,
transition: 'transform 10s ease-in-out',
}}><NewReleasesIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
null
}
</div>
</Grid>
<Grid item xs={4}>
<div style={{ position: 'relative' }}>
<img src="/images/adminpage/app1.svg" style={{ margin: 'auto' }} />
{checked ?
<div style={{
position: 'absolute',
bottom: 0,
left: '50%',
top: 30,
transform: 'translateX(-50%)',
backgroundColor: '#2f2f2f',
borderRadius: '50%',
height: 24,
width: 24,
transition: 'transform 10s ease-in-out',
}}><MailOutlineIcon style={{ width: 16, height: 16, marginTop: 5 }} /></div> :
null
}
</div>
</Grid>
</Grid>
</div>
</div>}
</div>
<div style={{ width: 595, height: 322, marginTop: 16, paddingTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
<div style={{ marginLeft: 18, textAlign: "start" }}>Workflows</div>
<div style={{textAlign:"center"}}>
<Button style={{ textTransform: "capitalize", background: selectedOption === 'all' ? "#494949" : "#1A1A1A", borderRadius: selectedOption === 'all' ? 15 : null, color: "#FFFFFF" }} onClick={() => handleOptionChange('all')}>All</Button>
<Button style={{ textTransform: "capitalize", background: selectedOption === 'revisions' ? "#494949" : "#1A1A1A", borderRadius: selectedOption === 'revisions' ? 15 : null, color: "#FFFFFF" }} onClick={() => handleOptionChange('revisions')}>Revisions</Button>
<Button style={{ textTransform: "capitalize", background: selectedOption === 'run' ? "#494949" : "#1A1A1A", borderRadius: selectedOption === 'run' ? 15 : null, color: "#FFFFFF" }} onClick={() => handleOptionChange('run')}>Runs</Button>
</div>
<Bar data={data} options={options} style={{ width: 400, marginLeft: 25, padding:20,marginTop: 10 }} />
</div>
</div>
<div style={{ width: 375, height: 440, display: 'flex', alignItems: 'start', paddingTop: '16px', paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
<div style={{ marginLeft: 18 }}>Insights</div>
</div>
</div>
<div style={{ width: 985, paddingTop: 16, marginTop: 16, paddingBottom: '48px', fontSize: '16px', color: '#ffffff', backgroundColor: '#1A1A1A', borderRadius: '16px', }}>
<div style={{ marginLeft: 18, textAlign: 'start', marginBottom: 16 }}>Sessions Overview</div>
<div style={{ display: "flex", width: "100%", justifyContent: "center" }}>
<div style={{ fontSize: '16px',width: "30%", borderRadius: 8, background: '#212121', color: '#9CA3AF' }}>
<div style={{ marginTop: 21, color: "#ffffff", marginLeft:20,fontSize: 16, fontWeight: "bold", }}>
2.8 Hours
</div>
<div style={{ marginTop: 13, marginBottom: 16, marginLeft:20, }}>
Avg. Activity per session
</div>
</div>
<div style={{ marginLeft: 16, fontSize: '16px', width: "30%", borderRadius: 8, background: '#212121', color: '#9CA3AF' }}>
<div style={{ marginTop: 21, marginLeft:20, color: "#ffffff", fontSize: 16, fontWeight: "bold", }}>
/usercases/edr to ticket
</div>
<div style={{ marginTop: 13, marginLeft:20, marginBottom: 16, }}>
Last visited page
</div>
</div>
<div style={{ marginLeft: 16, fontSize: '16px', width: "30%", borderRadius: 8, background: '#212121', color: '#9CA3AF' }}>
<div style={{ marginTop: 21, marginLeft:20,color: "#ffffff", fontSize: 16, fontWeight: "bold", }}>
/workflow/email management
</div>
<div style={{ marginTop: 13, marginLeft:20, marginBottom: 16, }}>
Most visited page
</div>
</div>
</div>
</div>
</div>
);
};
export default AnalyticsTab;
+1 -1
View File
@@ -67,7 +67,7 @@ import { Context } from '../context/ContextApi.jsx';
const searchClient = algoliasearch(
"JNSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240"
);
)
const AppAuthTab = memo((props) => {
const { globalUrl, userdata, isCloud, selectedOrganization } = props;
+214
View File
@@ -0,0 +1,214 @@
import React, { useState, useEffect, useRef } from 'react';
import theme from '../theme.jsx';
import { Link, useNavigate } from 'react-router-dom';
import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@mui/icons-material';
//import algoliasearch from 'algoliasearch/lite';
import algoliasearch from 'algoliasearch';
import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom';
import {
Grid,
Paper,
TextField,
InputAdornment,
Typography,
} from '@mui/material';
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
const Appsearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp, placeholder,
} = props
let navigate = useNavigate();
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs
const [open, setOpen] = React.useState(false);
const [value, setValue] = useState("");
window.title = "Shuffle | Apps | Find and integration any app"
const useCloseOnBlur = (setOpen) => {
useEffect(() => {
// Add event listener to detect clicks outside of the search box
const handleClickOutside = (event) => {
// Check if the click is outside the search box
if (!event.target.closest('.search-box')) {
setOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
// Clean up event listener
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [setOpen]); // Ensure that this effect runs whenever setOpen changes
};
useCloseOnBlur(setOpen);
const SearchBox = ({ currentRefinement, refine, isSearchStalled }) => {
useEffect(() => {
//console.log("FIRST LOAD ONLY? RUN REFINEMENT: !", currentRefinement)
if (defaultSearch !== undefined && defaultSearch !== null) {
refine(defaultSearch)
}
}, [])
return (
<div style={{ textAlign: isMobile ? "center" : "" }}>
<form noValidate action="" role="search">
<TextField
sx={{
'& input[type="search"]': {
filter:
'brightness(0) invert(1)',
},
}}
fullWidth
variant="standard"
style={{
width: isMobile ? 295 : 402,
borderRadius: 8,
border: 0,
boxShadow: 0,
margin: 10,
height: isMobile ? 51 : "",
textAlign: "center",
// border: "1px solid rgba(241.19, 241.19, 241.19, 0.10)",
boxShadow: "none",
backgroundColor: "rgba(241.19, 241.19, 241.19, 0.10)",
fontWeight: 400,
marginLeft: 10,
zIndex: 110,
}}
InputProps={{
disableUnderline: true,
style: {
fontSize: isMobile ? "0.8em" : "1em",
height: 50,
zIndex: 1100,
paddingLeft: 15,
},
endAdornment: (
<InputAdornment position="end">
<div
style={{
width: isMobile ? 42 : 42,
height: isMobile ? 36 : 36,
background: "#806BFF",
borderRadius: 8,
marginRight: 10,
}}
>
<SearchIcon
onClick={() => {
navigate("/search?q=" + currentRefinement, { state: value, replace: true })
//navigate("/search?q="+currentRefinement, { state: value, replace: true })
//window.open("/apps"+currentRefinement, "_blank")
}}
style={{ cursor: 'pointer', width: isMobile ? 20 : "", marginright: 5, marginTop: isMobile ? 6 : 7 }}
/>
</div>
</InputAdornment>
),
}}
autoComplete="off"
type="search"
color="primary"
placeholder={placeholder !== undefined ? placeholder : "Search more than 2500 Apps"}
id="shuffle_search_field"
onChange={(event) => {
// Remove "q" from URL
// removeQuery("q")
refine(event.currentTarget.value)
}}
onKeyDown={(event) => {
if (event.keyCode === 13) {
navigate("/search?q=" + currentRefinement, { state: value, replace: true });
}
}}
onClick={(event) => {
setOpen(true);
}}
/>
{/*isSearchStalled ? 'My search is stalled' : ''*/}
</form>
</div>
)
}
const Hits = ({ hits, currentRefinement }) => {
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1)
var counted = 0
return (
<Grid container spacing={0} style={{ display: "flex", alignItems: "center", justifyContent: "center", width: isMobile ? 302 : 402, maxHeight: 250, minHeight: 250, overflowY: "auto", overflowX: "hidden", }}>
{hits.map((data, index) => {
const paperStyle = {
backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,1)" : "#38383A",
color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)",
// border: newSelectedApp.objectID !== data.objectID ? `1px solid rgba(255,255,255,0.2)` : "2px solid #f86a3e",
textAlign: "left",
padding: 10,
cursor: "pointer",
position: "relative",
overflow: "hidden",
width: 402,
minHeight: 37,
maxHeight: 52,
}
if (counted === 12 / xs * rowHandler) {
return null
}
counted += 1
var parsedname = data.name.valueOf()
parsedname = (parsedname.charAt(0).toUpperCase() + parsedname.substring(1)).replaceAll("_", " ")
return (
<Paper key={index} elevation={0} style={paperStyle} onMouseOver={() => {
setMouseHoverIndex(index)
}} onMouseOut={() => {
setMouseHoverIndex(-1)
}} onClick={() => {
if (setNewSelectedApp !== undefined) {
setNewSelectedApp(data.name)
} else {
//need to add perfect url which redirect direct to app page
const newname = data.name.toLowerCase().replaceAll(" ", "_")
window.open("/apps/" + newname, "_blank")
}
}}>
<div style={{ display: "flex" }}>
<img alt={data.name} src={data.image_url} style={{ width: "100%", maxWidth: 30, minWidth: 30, minHeight: 30, borderRadius: 40, maxHeight: 30, display: "block", }} />
<Typography variant="body1" style={{ marginTop: 2, marginLeft: 10, }}>
{parsedname}
</Typography>
</div>
</Paper>
)
})}
</Grid>
)
}
const InputHits = ConfiguredHits === undefined ? Hits : ConfiguredHits
const CustomSearchBox = connectSearchBox(SearchBox)
const CustomHits = connectHits(InputHits)
return (
<div className="search-box" style={{ width: isMobile ? null : "100%", height: 95, alignItems: "center", justifyContent: "center", gap: 138, zIndex: 11000, }}>
<InstantSearch searchClient={searchClient} indexName="appsearch">
<div style={{ maxWidth: 450, margin: "auto", }}>
<CustomSearchBox />
</div>
<div style={{ alignItems: "center", justifyContent: "center", width: "100%", display: "flex" }}>
{open ? <CustomHits hitsPerPage={1} /> : null}
</div>
</InstantSearch>
</div>
)
}
export default Appsearch;
+3 -3
View File
@@ -343,14 +343,14 @@ const AppStats = (defaultprops) => {
*/}
</div>
{clickData === undefined ?
{clickData === undefined || clickData === null || clickData?.length === 0 ?
null
:
<LineChartWrapper keys={clickData} height={300} width={"100%"} inputname={"Clicks"}/>
}
<div style={{marginTop: 25, }} />
{conversionData === undefined ?
{conversionData === undefined || conversionData === null || conversionData?.length === 0 ?
null
:
<LineChartWrapper keys={conversionData} height={300} width={"100%"} inputname={"Conversions"}/>
@@ -365,4 +365,4 @@ const AppStats = (defaultprops) => {
return dataWrapper;
}
export default AppStats;
export default AppStats;
+135 -22
View File
@@ -39,6 +39,7 @@ import {
import { toast } from 'react-toastify';
import { Context } from '../context/ContextApi.jsx';
import { green, red } from '../views/AngularWorkflow.jsx'
import AppSearch from "../components/AppSearch1.jsx";
const EnvironmentTab = memo((props) => {
const { globalUrl, isCloud, userdata, selectedOrganization } = props;
@@ -59,6 +60,9 @@ const EnvironmentTab = memo((props) => {
const [showDistributionPopup, setShowDistributionPopup] = React.useState(false);
const [selectedEnvironment, setSelectedEnvironment] = React.useState(null);
const [selectedSubOrg, setSelectedSubOrg] = React.useState([]);
const [showLocationActionModal, setShowLocationActionModal] = React.useState(undefined)
useEffect(() => {
getEnvironments();
setModalUser({});
@@ -420,7 +424,9 @@ const EnvironmentTab = memo((props) => {
-e SHUFFLE_WORKER_IMAGE="ghcr.io/shuffle/shuffle-worker:nightly" \\
-e SHUFFLE_SWARM_CONFIG=run \\
-e SHUFFLE_LOGS_DISABLED=true \\
-e BASE_URL="${newUrl}" \\${addProxy ? "\n -e HTTPS_PROXY=IP:PORT \\" : ""}${skipPipeline ? "\n -e SHUFFLE_SKIP_PIPELINES=true \\" : ""}
-e BASE_URL="${newUrl}" \\${addProxy ? `
-e HTTPS_PROXY=IP:PORT \\` : ""}${skipPipeline ? `
-e SHUFFLE_SKIP_PIPELINES=true \\` : ""}
ghcr.io/shuffle/shuffle-orborus:latest
`)
} else if (installationTab === 2) {
@@ -435,7 +441,9 @@ const EnvironmentTab = memo((props) => {
-e AUTH="${auth}" \\
-e ENVIRONMENT_NAME="${environment.Name}" \\
-e ORG="${props.userdata.active_org.id}" \\
-e BASE_URL="${newUrl}" \\${addProxy ? "\n -e HTTPS_PROXY=IP:PORT \\" : ""}${skipPipeline ? "\n -e SHUFFLE_SKIP_PIPELINES=true \\" : ""}
-e BASE_URL="${newUrl}" \\${addProxy ? `
-e HTTPS_PROXY=IP:PORT \\` : ""}${skipPipeline ? `
-e SHUFFLE_SKIP_PIPELINES=true \\` : ""}
ghcr.io/shuffle/shuffle-orborus:latest`
return commandData
@@ -592,6 +600,70 @@ const EnvironmentTab = memo((props) => {
return queue;
};
const LocationActionModal = (props) => {
const { showLocationActionModal } = props
const [searchQuery, setSearchQuery] = React.useState("");
if (showLocationActionModal === undefined || showLocationActionModal === null) {
return null
}
if (showLocationActionModal?.open !== true) {
return null
}
return (
<Dialog
open={true}
onClose={() => {
setShowLocationActionModal(undefined)
}}
PaperProps={{
sx: {
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
border: theme?.palette?.DialogStyle?.border,
fontFamily: theme?.typography?.fontFamily,
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
zIndex: 1000,
minWidth: 600,
minHeight: 500,
overflow: "auto",
'& .MuiDialogContent-root': {
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
},
'& .MuiDialogTitle-root': {
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
},
'& .MuiDialogActions-root': {
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
},
},
}}
>
<DialogTitle>
<div style={{ color: "rgba(255,255,255,0.9)" }}>
Select a job to send
</div>
</DialogTitle>
<DialogContent style={{ backgroundColor: theme.palette.backgroundColor }}>
<Checkbox
checked={true}
disabled={true}
/>
<span style={{ marginLeft: 8 }}>
Find app to re-download
</span>
<AppSearch
/>
</DialogContent>
</Dialog>
)
}
const editEnvironmentConfig = (id, selectedSubOrg, cacheKey) => {
const data = {
action: "suborg_distribute",
@@ -804,20 +876,23 @@ const EnvironmentTab = memo((props) => {
borderBottom: "1px solid #494949",
}}
>
{["Type", "Status", "Scale", "Pipeline", "Name", "Type", "Queue", "Actions", "Distribution"].map((header, index) => (
<ListItemText
key={index}
primary={header}
style={{
padding: "0px 8px 8px 8px",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
overflow: "hidden",
fontWeight: "bold",
textAlign: "center",
}}
/>
))}
{["Type", "Status", "Scale", "Pipeline", "Name", "Type", "Queue", "Actions", "Distribution"].map((header, index) => {
return (
<ListItemText
key={index}
primary={header}
style={{
padding: "0px 8px 8px 8px",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
overflow: "hidden",
fontWeight: "bold",
textAlign: header === "Actions" ? "left" : header === "Distribution" ? "right" : "center",
}}
/>
)
})}
</ListItem>
{showLoader
? [...Array(6)].map((_, rowIndex) => (
@@ -919,9 +994,10 @@ const EnvironmentTab = memo((props) => {
return (
<>
<ListItem
key={index}
style={{ cursor: "pointer", backgroundColor: bgColor, marginLeft: 0, borderBottomLeftRadius: environments?.length - 1 === index ? 8 : 0, borderBottomRightRadius: environments?.length - 1 === index ? 8 : 0, display: 'grid', gridTemplateColumns: "80px 80px 80px 120px 120px 120px 120px 350px 150px", }}
style={{ cursor: "pointer", backgroundColor: bgColor, marginLeft: 0, borderBottomLeftRadius: environments?.length - 1 === index ? 8 : 0, borderBottomRightRadius: environments?.length - 1 === index ? 8 : 0, display: 'grid', gridTemplateColumns: "80px 80px 80px 120px 120px 120px 120px 405px 125px", }}
onClick={() => {
if (environment.Type === "cloud") {
toast("Cloud environments are not configurable. To see what is possible, create a new environment.")
@@ -1156,7 +1232,7 @@ const EnvironmentTab = memo((props) => {
primary={environment.Type}
primaryTypographyProps={{
style:{
minWidth: 100,
minWidth: 70,
overflow: "hidden",
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
@@ -1228,6 +1304,7 @@ const EnvironmentTab = memo((props) => {
>
{environment.archived ? "Activate" : "Disable"}
</Button>
<Button
variant={"outlined"}
disabled={selectedOrganization.id !== environment.org_id}
@@ -1254,7 +1331,34 @@ const EnvironmentTab = memo((props) => {
? "Rerun"
: "Clear"}
</Button>
{environment.Type === "cloud" ? null :
<Button
variant={"outlined"}
style={{
fontSize: 16,
textTransform: 'none',
whiteSpace: "nowrap", // Prevent text wrapping
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
toast.info("Please choose an app you want to re-distribute to this environment")
setShowLocationActionModal({
environment: environment,
open: true,
})
setUpdate(Math.random())
}}
color="primary"
>
Send Job
</Button>
}
</ButtonGroup>
<IconButton disabled={environment.Type === "cloud"} onClick={()=> {setIsExpanded(prev => !prev)}}>
{listItemExpanded === index ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
@@ -1429,7 +1533,7 @@ const EnvironmentTab = memo((props) => {
}
setCommandController(commandController)
setUpdate(Math.random())
setUpdate(Math.random())
}}
/>
<div />
@@ -1544,11 +1648,20 @@ const EnvironmentTab = memo((props) => {
) }
</List>
</div>
{/* <EnvironmentStats /> */}
</div>
</div>
</div>
</div>
{showLocationActionModal !== undefined && showLocationActionModal !== null && showLocationActionModal?.open === true ?
<div>
<LocationActionModal
showLocationActionModal={showLocationActionModal}
/>
</div>
: null }
</div>
)
});
+3 -3
View File
@@ -243,7 +243,7 @@ const ApiExplorerWrapper = (props) => {
.then((responseJson) => {
if (responseJson.success === true) {
if (responseJson.openapi === undefined || responseJson.openapi === null) {
toast.warning("Loaded App, but no API found. Redirecting to app search..")
toast.warning("Loaded App, but no API found. Redirecting back to app..")
navigate(`/apps/${appid}`)
} else {
handleDecodeOfOpenApiData(responseJson);
@@ -378,7 +378,7 @@ const ApiExplorerWrapper = (props) => {
const parsedHeaders = {};
if (typeof headers === 'string' && headers) {
const splitHeaders = headers.split("\n");
const splitHeaders = headers.split(`\n`)
splitHeaders.forEach(header => {
let splitItem;
@@ -529,7 +529,7 @@ const ApiExplorerWrapper = (props) => {
return array
.map(item => (item.key.trim().length > 0 && item.value.trim().length > 0 ? `${item.key}=${item.value}` : ""))
.filter(str => str.length > 0)
.join("\n");
.join(``);
};
var appid = "";
+51 -14
View File
@@ -226,7 +226,7 @@ const AppExplorer = (props) => {
const [anchorEl, setAnchorEl] = React.useState(null);
const [creatorProfile, setCreatorProfile] = React.useState({});
const [selectedTab, setSelectedTab] = React.useState(0);
const defaultDocs = "\n\n## No Shuffle-specific app documentation is available yet.\n\n## Need more information about the app? [Contact us](/contact) and [Join the Community](https://discord.gg/B2CBzUm) and find others using this app."
const defaultDocs = `\n\n## No Shuffle-specific app documentation is available yet.\n\n## Need more information about the app? [Contact us](/contact) and [Join the Community](https://discord.gg/B2CBzUm) and find others using this app.`
const [sharingConfiguration, setSharingConfiguration] = React.useState("you");
const [appdata, setAppData] = React.useState({});
const [appDocumentation, setAppDocumentation] = useState(defaultDocs)
@@ -739,13 +739,16 @@ const AppExplorer = (props) => {
}
};
const activateApp = () => {
const activateApp = (action) => {
if (serverside === true) {
return;
return
}
const appExists = userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appId)
const url = appExists ? `${globalUrl}/api/v1/apps/${appId}/deactivate` : `${globalUrl}/api/v1/apps/${appId}/activate`
var url = appExists ? `${globalUrl}/api/v1/apps/${appId}/deactivate` : `${globalUrl}/api/v1/apps/${appId}/activate`
if (action !== undefined && action !== null) {
url = `${globalUrl}/api/v1/apps/${appId}/${action}`
}
fetch(url, {
method: "GET",
headers: {
@@ -763,21 +766,32 @@ const AppExplorer = (props) => {
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
toast("Failed to activate the app: "+responseJson.reason);
} else {
toast("Failed to activate the app");
}
if (action === undefined || action === null) {
if (responseJson.reason !== undefined) {
toast("Failed to activate the app: "+responseJson.reason);
} else {
toast("Failed to activate the app");
}
} else {
if (responseJson.reason !== undefined) {
toast("Failed to perform action: "+responseJson.reason);
} else {
toast("Failed to perform action. Please try again or contact support@shuffler.io");
}
}
} else {
if (checkLogin !== undefined && checkLogin !== null) {
checkLogin()
}
if (appExists) {
toast("App deactivated for your organization! Existing workflows with the app will continue to work.")
} else {
toast("App activated for your organization!")
}
if (action === undefined || action === null) {
if (appExists) {
toast("App deactivated for your organization! Existing workflows with the app will continue to work.")
} else {
toast("App activated for your organization!")
}
} else {
}
}
})
.catch((error) => {
@@ -4028,6 +4042,29 @@ const AppExplorer = (props) => {
</IconButton>
: null}
{isMobile || userdata?.active_apps === undefined || userdata?.active_apps === null || !userdata?.active_apps?.includes(appId) ? null :
<Button
variant={userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appId) ? "outlined": "contained"}
component="label"
color="primary"
onClick={() => {
if (!isLoggedIn) {
//navigate("/login?message=You must be logged in to activate this app&view=/apps/" + params.appid);
toast("You must be logged in to activate apps! Go to /login first.")
return;
}
toast.info("Sending download job to relevant runtime locations")
activateApp("distribute")
}}
style={{ height: 40, marginTop: 5 }}
>
Push Image
</Button>
}
{isMobile ? null : (
<Button
variant={userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appId) ? "outlined": "contained"}