Optimized Api Explorer and testing system
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -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"}
|
||||
|
||||
Reference in New Issue
Block a user