Merge branch '1.4.0'

This commit is contained in:
Aditya
2023-11-16 01:15:44 +05:30
24 changed files with 3114 additions and 1124 deletions
+33 -10
View File
@@ -291,8 +291,6 @@ const Admin = (props) => {
//const alert = useAlert();
const handleStatusChange = (event) => {
const { value } = event.target;
console.log("value: ", value)
setSelectedStatus(value);
@@ -331,7 +329,7 @@ const Admin = (props) => {
var your_apps = "- Connecting "
var subject_add = 0
var subject = "Want to automate "
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 !== "") {
@@ -400,7 +398,7 @@ const Admin = (props) => {
// Remove comma
subject += "?"
//subject += "?"
your_apps = your_apps.substring(0, your_apps.length - 2)
}
@@ -432,12 +430,25 @@ const Admin = (props) => {
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)
@@ -452,15 +463,24 @@ const Admin = (props) => {
// Get drift username from userdata.username before @ in email
const username = userdata.username.substring(0, userdata.username.indexOf("@"))
var body = `Hey,%0D%0A%0D%0AI saw you trying to use Shuffle, and thought we may be able to help. Right now, it looks like you have ${workflow_amount} workflows made, but it still doesn't look like you are getting the most 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
// 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}
- Creating special usecases and apps%0D%0A%0D%0A
- Multi-Tenancy and creating special usecases%0D%0A
${cloudSyncInfo}%0D%0A
Let me know if you're interested, or set up a call here: https://drift.me/${username}`
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}`
}
@@ -984,6 +1004,10 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user
leads.push("old customer")
}
if (responseJson.lead_info.old_lead) {
leads.push("old lead")
}
if (responseJson.lead_info.tech_partner) {
leads.push("tech partner")
}
@@ -2428,7 +2452,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user
renderValue={(selected) => selected.join(', ')}
MenuProps={MenuProps}
>
{["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "old customer", "creator", "tech partner"].map((name) => (
{["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "creator", "tech partner", "old customer", "old lead", ].map((name) => (
<MenuItem key={name} value={name}>
<Checkbox checked={selectedStatus.indexOf(name) > -1} />
<ListItemText primary={name} />
@@ -3124,7 +3148,6 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user
}
if (loginInfo > 0) {
console.log("SETTING LAST LOGIN: " + loginInfo)
lastLogin = new Date(loginInfo * 1000).toISOString().slice(0, 10) + " (" + data.login_info.length + ")"
}
}
@@ -4382,7 +4405,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user
<Tab
label=<span>
<LockIcon style={iconStyle} />
App Authentication
App Auth
</span>
/>
<Tab
+38 -22
View File
@@ -108,10 +108,10 @@ import {
Preview as PreviewIcon,
ContentCopy as ContentCopyIcon,
Circle as CircleIcon,
SquareFoot as SquareFootIcon,
AutoFixHigh as AutoFixHighIcon,
SquareFoot as SquareFootIcon,
AutoFixHigh as AutoFixHighIcon,
Polyline as PolylineIcon,
QueryStats as QueryStatsIcon,
} from "@mui/icons-material";
@@ -591,7 +591,7 @@ const AngularWorkflow = (defaultprops) => {
}
useEffect(() => {
console.log("In useeffect for loopRunning: ", loopRunning)
//console.log("In useeffect for loopRunning: ", loopRunning)
if (loopRunning) {
const intervalId = setInterval(() => {
if (!loopRunning) {
@@ -1244,7 +1244,7 @@ const AngularWorkflow = (defaultprops) => {
// Doesn't work because this is some async garbage
if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) {
if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) {
console.log("Updating data!")
//console.log("Updating data!")
setExecutionData(responseJson)
} else {
if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING") {
@@ -1713,7 +1713,9 @@ const AngularWorkflow = (defaultprops) => {
})
.then((responseJson) => {
if (!responseJson.success) {
toast("Failed to start: " + responseJson.reason);
//toast("Failed to start: " + responseJson.reason);
toast(responseJson.reason);
//toast.error(responseJson.reason);
setExecutionRunning(false);
setExecutionRequestStarted(false);
stop();
@@ -4593,7 +4595,6 @@ const AngularWorkflow = (defaultprops) => {
}
}
console.log("NODE: ", nodedata)
if (nodedata.decorator !== true && nodedata.attachedTo === undefined) {
var newdata = JSON.parse(JSON.stringify(nodedata))
newdata.large_image = ""
@@ -14591,7 +14592,7 @@ const AngularWorkflow = (defaultprops) => {
onClose={() => {
setExecutionModalOpen(false)
}}
style={{ resize: "both", overflow: "auto", zIndex: 10005 }}
style={{ resize: "both", overflow: "auto", }}
hideBackdrop={false}
variant="temporary"
BackdropProps={{
@@ -14607,7 +14608,6 @@ const AngularWorkflow = (defaultprops) => {
maxWidth: isMobile ? "100%" : 420,
color: "white",
fontSize: 18,
zIndex: 10005,
borderLeft: theme.palette.defaultBorder,
},
}}
@@ -14631,16 +14631,32 @@ const AngularWorkflow = (defaultprops) => {
: null}
{executionModalView === 0 ? (
<div style={{ padding: isMobile ? "0px 0px 0px 10px" : 25, zIndex: 12502, }}>
<Breadcrumbs
aria-label="breadcrumb"
separator=""
style={{ color: "white", fontSize: 16 }}
>
<h2 style={{ color: "rgba(255,255,255,0.5)" }}>
<DirectionsRunIcon style={{ marginRight: 10 }} />
All Workflow Runs
</h2>
</Breadcrumbs>
<div style={{display: "flex", }}>
<Breadcrumbs
aria-label="breadcrumb"
separator=""
style={{ color: "white", fontSize: 16 }}
>
<h2 style={{ color: "rgba(255,255,255,0.5)" }}>
<DirectionsRunIcon style={{ marginRight: 10 }} />
All Workflow Runs
</h2>
</Breadcrumbs>
<Tooltip
title={"Explore Executions further"}
placement="left-start"
style={{ zIndex: 10010 }}
>
<a href={`/workflows/debug?workflow_id=${workflow.id}`} style={{textDecoration: "none", }}>
<Button
color="secondary"
style={{marginLeft: 50, maxHeight: 30, marginTop: 20, }}
>
<QueryStatsIcon style={{color: "white", }}/>
</Button>
</a>
</Tooltip>
</div>
<Button
style={{ borderRadius: "0px" }}
variant="outlined"
@@ -15367,7 +15383,7 @@ const AngularWorkflow = (defaultprops) => {
}}
onClick={() => {
const oldstartnode = cy.getElementById(data.action.id);
console.log("FOUND NODe: ", oldstartnode)
//console.log("FOUND NODe: ", oldstartnode)
if (oldstartnode !== undefined && oldstartnode !== null) {
const foundname = oldstartnode.data("label")
if (foundname !== undefined && foundname !== null) {
@@ -15375,7 +15391,7 @@ const AngularWorkflow = (defaultprops) => {
}
}
console.log("Click data: ", data)
//console.log("Click data: ", data)
//data.action.label = ""
setSelectedResult(data);
setCodeModalOpen(true);
@@ -16476,7 +16492,7 @@ const AngularWorkflow = (defaultprops) => {
var newAuthOption = JSON.parse(JSON.stringify(authenticationOption));
var newFields = [];
console.log("Fields: ", newAuthOption.fields)
console.log("Fields: ", newAuthOption.fields)
for (let authkey in newAuthOption.fields) {
const value = newAuthOption.fields[authkey];
newFields.push({
+223 -141
View File
@@ -242,7 +242,7 @@ export const appCategories = [
"name": "Eradication",
"color": "#FFC107",
"icon": "eradication",
"action_labels": ["List Alerts", "Close Alert", "Get Alert", "Create detection", "Block hash", "Search Hosts", "Isolate host", "Unisolate host"],
"action_labels": ["List Alerts", "Close Alert", "Get Alert", "Create detection", "Block hash", "Search Hosts", "Isolate host", "Unisolate host", "Trigger host scan",],
}, {
"name": "Cases",
"color": "#FFC107",
@@ -374,8 +374,8 @@ const AppCreator = (defaultprops) => {
"API key",
"Bearer auth",
"Basic auth",
"Oauth2",
"JWT",
"Oauth2",
];
const apikeySelection = ["Header", "Query"];
@@ -411,6 +411,7 @@ const AppCreator = (defaultprops) => {
const [appBuilding, setAppBuilding] = useState(false);
const [fileDownloadEnabled, setFileDownloadEnabled] = useState(false);
const [actionAmount, setActionAmount] = useState(increaseAmount);
const [oauth2Type, setOauth2Type] = useState("application");
const defaultAuth = {
name: "",
type: "header",
@@ -1678,7 +1679,7 @@ const AppCreator = (defaultprops) => {
value.in.length > 0
) {
setParameterName(value.in);
optionset = true
optionset = true
}
} else if (value.scheme === "bearer") {
@@ -1709,24 +1710,24 @@ const AppCreator = (defaultprops) => {
newauth.push({
"name": key,
"type": value.in.toLowerCase(),
"in": value.in.toLowerCase(),
"in": value.in.toLowerCase(),
"example": "",
})
} else {
newauth.push({
"name": key,
"type": value.in.toLowerCase(),
"in": value.in.toLowerCase(),
"example": "",
})
}
})
} else {
newauth.push({
"name": key,
"type": value.in.toLowerCase(),
"in": value.in.toLowerCase(),
"example": "",
})
}
if (value.description !== undefined && value.description !== null && value.description.length > 0) {
// Don't want a real description - just the ones we're replacing with
if ((value.description.split(" ").length - 1) <= 2) {
setRefreshUrl(value.description)
}
}
// Don't want a real description - just the ones we're replacing with
if ((value.description.split(" ").length - 1) <= 2) {
setRefreshUrl(value.description)
}
}
} else if (value.scheme === "basic") {
setAuthenticationOption("Basic auth");
@@ -1734,15 +1735,15 @@ const AppCreator = (defaultprops) => {
optionset = true
} else if (value.scheme === "oauth2") {
setAuthenticationOption("Oauth2");
setAuthenticationRequired(true);
optionset = true
setAuthenticationOption("Oauth2");
setAuthenticationRequired(true);
optionset = true
} else if (value.type === "oauth2" || key === "Oauth2" || key === "Oauth2c" || (key !== undefined && key !== null && key.toLowerCase().includes("oauth2"))) {
//toast("Can't handle Oauth2 auth yet.")
setAuthenticationOption("Oauth2");
setAuthenticationRequired(true);
optionset = true
optionset = true
//console.log("FLOW-1: ", value)
const flowkey = value.flow === undefined ? "flows" : "flow";
@@ -1754,12 +1755,11 @@ const AppCreator = (defaultprops) => {
//console.log("FLOW2: ", value[flowkey][basekey])
if (value[flowkey] !== undefined && value[flowkey][basekey] !== undefined
) {
if (
value[flowkey][basekey].authorizationUrl !== undefined &&
parameterName.length === 0
) {
setParameterName(value[flowkey][basekey].authorizationUrl);
}
if (value[flowkey][basekey].authorizationUrl !== undefined && parameterName.length === 0) {
setParameterName(value[flowkey][basekey].authorizationUrl);
// } else {
// setOauth2Type("application")
}
var tokenUrl = "";
if (value[flowkey][basekey].tokenUrl !== undefined) {
@@ -2846,7 +2846,6 @@ const AppCreator = (defaultprops) => {
style={{ margin: 0, flex: "1", backgroundColor: inputColor }}
fullWidth={true}
placeholder="/security/user/authenticate"
type="name"
id="standard-required"
margin="normal"
variant="outlined"
@@ -2866,6 +2865,40 @@ const AppCreator = (defaultprops) => {
},
}}
/>
<Typography
variant="body2"
color="textSecondary"
style={{ marginTop: 10 }}
>
Optional: Authentication queries
</Typography>
{/*
<TextField
style={{ margin: 0, flex: "1", backgroundColor: inputColor }}
fullWidth={true}
placeholder="grant_type=client_credentials&scope=connect.api.read"
id=""
margin="normal"
variant="outlined"
defaultValue={parameterName}
helperText={
<span style={{ color: "white", marginBottom: "2px" }}>
Must use 'key=value&key=value' format
</span>
}
onBlur={(e) => {
//setParameterName(e.target.value)
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
*/}
</div>
) : null;
@@ -2878,56 +2911,64 @@ const AppCreator = (defaultprops) => {
color="textSecondary"
style={{ marginTop: 10 }}
>
Find the Authorization URL, Token URL and scopes in question for the API. Ensure the app in question is pointed at https://shuffler.io/set_authentication
{oauth2Type === "delegated" ?
"Find the Authorization URL, Token URL and scopes in question for the API. Ensure your app in the service uses redirect url https://shuffler.io/set_authentication"
:
"Find the Token URL and scopes in question for the API"
}
</Typography>
<Typography
variant="body2"
color="textSecondary"
style={{ marginTop: 10 }}
>
Base Authorization URL for Oauth2
</Typography>
<TextField
required
style={{ margin: 0, flex: "1", backgroundColor: inputColor }}
fullWidth={true}
placeholder="https://.../oauth2/authorize"
type="name"
id="standard-required"
margin="normal"
variant="outlined"
value={parameterName}
onChange={(e) => setParameterName(e.target.value)}
{oauth2Type === "delegated" ?
<span>
<Typography
variant="body2"
color="textSecondary"
style={{ marginTop: 10 }}
>
Base Authorization URL for Oauth2
</Typography>
<TextField
required
style={{ margin: 0, flex: "1", backgroundColor: inputColor }}
fullWidth={true}
placeholder="https://.../oauth2/authorize"
type="name"
id="standard-required"
margin="normal"
variant="outlined"
value={parameterName}
onChange={(e) => setParameterName(e.target.value)}
onBlur={(event) => {
var tmpstring = event.target.value.trim();
var tmpstring = event.target.value.trim();
if (
tmpstring.length > 4 &&
!tmpstring.startsWith("http") &&
!tmpstring.startsWith("ftp")
) {
toast("Auth URL must start with http(s)://");
}
if (
tmpstring.length > 4 &&
!tmpstring.startsWith("http") &&
!tmpstring.startsWith("ftp")
) {
toast("Auth URL must start with http(s)://");
}
if (tmpstring.includes("?")) {
var newtmp = tmpstring.split("?")
if (tmpstring.length > 1) {
tmpstring = newtmp[0]
}
}
if (tmpstring.includes("?")) {
var newtmp = tmpstring.split("?")
if (tmpstring.length > 1) {
tmpstring = newtmp[0]
}
}
setParameterName(tmpstring)
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
setParameterName(tmpstring)
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
: null}
<Typography
variant="body2"
color="textSecondary"
@@ -2977,75 +3018,79 @@ const AppCreator = (defaultprops) => {
},
}}
/>
<Typography
variant="body2"
color="textSecondary"
style={{ marginTop: 10 }}
>
Refresh-token URL for Oauth2 (Optional)
</Typography>
<TextField
style={{ margin: 0, flex: "1", backgroundColor: inputColor }}
fullWidth={true}
placeholder="The URL to retrieve refresh-tokens at"
type="name"
id="standard-required"
margin="normal"
variant="outlined"
value={refreshUrl}
onChange={(e) => setRefreshUrl(e.target.value)}
onBlur={(event) => {
var tmpstring = event.target.value.trim();
{oauth2Type === "delegated" ?
<span>
<Typography
variant="body2"
color="textSecondary"
style={{ marginTop: 10 }}
>
Refresh-token URL for Oauth2 (Optional)
</Typography>
<TextField
style={{ margin: 0, flex: "1", backgroundColor: inputColor }}
fullWidth={true}
placeholder="The URL to retrieve refresh-tokens at"
type="name"
id="standard-required"
margin="normal"
variant="outlined"
value={refreshUrl}
onChange={(e) => setRefreshUrl(e.target.value)}
onBlur={(event) => {
var tmpstring = event.target.value.trim();
if (
tmpstring.length > 4 &&
!tmpstring.startsWith("http") &&
!tmpstring.startsWith("ftp")
) {
toast("Refresh URL must start with http(s)://");
}
if (
tmpstring.length > 4 &&
!tmpstring.startsWith("http") &&
!tmpstring.startsWith("ftp")
) {
toast("Refresh URL must start with http(s)://");
}
if (tmpstring.includes("?")) {
var newtmp = tmpstring.split("?")
if (tmpstring.length > 1) {
tmpstring = newtmp[0]
}
}
if (tmpstring.includes("?")) {
var newtmp = tmpstring.split("?")
if (tmpstring.length > 1) {
tmpstring = newtmp[0]
}
}
setRefreshUrl(tmpstring)
}}
InputProps={{
style: {
color: "white",
},
}}
/>
<Typography
variant="body2"
color="textSecondary"
style={{ marginTop: 10 }}
>
Scopes for Oauth2
</Typography>
<MuiChipsInput
style={{border: "2px solid #f86a3e", borderRadius: theme.palette.borderRadius,}}
setRefreshUrl(tmpstring)
}}
InputProps={{
style: {
color: "white",
},
}}
/>
</span>
: null}
<Typography
variant="body2"
color="textSecondary"
style={{ marginTop: 10 }}
>
Scopes for Oauth2
</Typography>
<MuiChipsInput
style={{border: "2px solid #f86a3e", borderRadius: theme.palette.borderRadius,}}
required
InputProps={{
style: {
color: "white",
maxHeight: 50,
},
}}
style={{ maxHeight: 80, overflowX: "hidden", overflowY: "auto" }}
placeholder="Available Oauth2 Scopes"
color="primary"
fullWidth
value={oauth2Scopes}
InputProps={{
style: {
color: "white",
maxHeight: 50,
},
}}
style={{ maxHeight: 80, overflowX: "hidden", overflowY: "auto" }}
placeholder="Available Oauth2 Scopes (enter to add)"
color="primary"
fullWidth
value={oauth2Scopes}
onChange={(chips) => {
setOauth2Scopes(chips)
setUpdate(Math.random())
}}
/>
/>
</div>
) : null;
@@ -3125,7 +3170,7 @@ const AppCreator = (defaultprops) => {
</Select>
</div>
<div style={{marginLeft: 5, flex: 1,}}>
Value prefix
Prefix
<TextField
style={{ marginTop: 0, flex: "1", backgroundColor: inputColor }}
fullWidth={true}
@@ -3137,7 +3182,7 @@ const AppCreator = (defaultprops) => {
value={refreshUrl}
onChange={(e) => {
// Just reusing this state
setRefreshUrl(e.target.value);
setRefreshUrl(e.target.value);
}}
/>
</div>
@@ -3618,13 +3663,13 @@ const AppCreator = (defaultprops) => {
<Dialog
open={actionsModalOpen}
fullWidth
PaperProps={{
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: 500,
maxWidth: 500,
maxHeight: 800,
minWidth: 550,
maxWidth: 550,
maxHeight: 750,
},
}}
onClose={() => {
@@ -5865,6 +5910,7 @@ const AppCreator = (defaultprops) => {
}}
/>
<div style={{padding: 25, border: "2px solid rgba(255,255,255,0.7)", borderRadius: theme.palette.borderRadius, }}>
<span style={{display: "flex", }}>
<FormControl style={{ }} variant="outlined">
<Typography variant="h6">Authentication</Typography>
<a
@@ -5889,7 +5935,7 @@ const AppCreator = (defaultprops) => {
setParameterLocation("")
}
setExtraAuth([])
setExtraAuth([])
}
}}
value={authenticationOption}
@@ -5910,6 +5956,42 @@ const AppCreator = (defaultprops) => {
))}
</Select>
</FormControl>
{authenticationOption === "Oauth2" ?
<FormControl style={{ marginLeft: 350, maxWidth: 200, }} variant="outlined">
{/*
<Typography variant="body2">
- Delegated: The user will get a popup for access their personal data.
- Application: Permissions are set by the app creator in the 3rd party platform.
</Typography>
*/}
<Select
fullWidth
label="Oauth2 type"
onChange={(e) => {
setOauth2Type(e.target.value);
}}
value={oauth2Type}
style={{
backgroundColor: inputColor,
color: "white",
height: "50px",
}}
>
{["delegated", "application"].map((data, index) => (
<MenuItem
key={index}
style={{ backgroundColor: inputColor, color: "white" }}
value={data}
>
{data}
</MenuItem>
))}
</Select>
</FormControl>
: null}
</span>
<div style={{marginTop: 15 }} />
{basicAuth}
{bearerAuth}
+1 -1
View File
@@ -419,7 +419,7 @@ const Welcome = (props) => {
</div>
:
<Fade in={true}>
<div style={{maxWidth: isMobile ? null : 590, margin: "auto", marginTop: 50, }}>
<div style={{maxWidth: isMobile ? null : 590, margin: "auto", marginTop: 50,textAlign : isMobile ? "center" : null }}>
<Typography variant="h4" style={{color: "#F1F1F1", marginTop: 50, fontSize: 32}}>
Help us get to know you
</Typography>
+19 -6
View File
@@ -68,11 +68,12 @@ import {
CloudDownload as CloudDownloadIcon,
ExpandLess as ExpandLessIcon,
ExpandMore as ExpandMoreIcon,
Done as DoneIcon,
CheckCircle as CheckCircleIcon,
RadioButtonUnchecked as RadioButtonUncheckedIcon,
Done as DoneIcon,
CheckCircle as CheckCircleIcon,
RadioButtonUnchecked as RadioButtonUncheckedIcon,
ArrowLeft as ArrowLeftIcon,
ArrowRight as ArrowRightIcon,
QueryStats as QueryStatsIcon,
} from "@mui/icons-material";
import { DataGrid, GridToolbar } from "@mui/x-data-grid";
@@ -3061,6 +3062,18 @@ const Workflows = (props) => {
const workflowButtons = (
<span>
<Tooltip color="primary" title={"Explore Workflow Runs"} placement="top">
<Button
disabled={workflows.length === 0}
color="secondary"
variant="text"
onClick={() => {
navigate("/workflows/debug")
}}
>
<QueryStatsIcon />
</Button>
</Tooltip>
{view === "list" && (
<Tooltip color="primary" title={"Grid View"} placement="top">
<Button
@@ -3423,8 +3436,8 @@ const Workflows = (props) => {
height: isCloud ? imgSize : imgSize+4,
width: isCloud ? imgSize : imgSize+4,
position: "absolute",
top: isCloud ? -1 : -2,
left: isCloud ? -1 : -2,
top: -2,
left: -2,
cursor: "pointer",
zIndex: 99,
border: "2px solid rgba(255,255,255,0.7)",
@@ -3786,7 +3799,7 @@ const Workflows = (props) => {
color: "white",
fontSize: 18,
borderLeft: theme.palette.defaultBorder,
marginTop: 64,
marginTop: 100,
borderRadius: "5px 0px 0px 0px",
},
}}