Fixed workflow X use case info
This commit is contained in:
@@ -2,7 +2,7 @@ module main
|
|||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
||||||
|
|
||||||
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
||||||
//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch
|
//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch
|
||||||
@@ -24,7 +24,7 @@ require (
|
|||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da // indirect
|
github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da // indirect
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/shuffle/shuffle-shared v0.2.6
|
github.com/shuffle/shuffle-shared v0.2.7
|
||||||
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
|
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
|
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
|
||||||
google.golang.org/api v0.65.0
|
google.golang.org/api v0.65.0
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
backend:
|
backend:
|
||||||
build: ./backend
|
#build: ./backend
|
||||||
image: ghcr.io/frikky/shuffle-backend:nightly
|
image: ghcr.io/frikky/shuffle-backend:nightly
|
||||||
container_name: shuffle-backend
|
container_name: shuffle-backend
|
||||||
hostname: ${BACKEND_HOSTNAME}
|
hostname: ${BACKEND_HOSTNAME}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,202 @@
|
|||||||
|
import React, {useState, useRef, useImperativeHandle} from 'react'
|
||||||
|
import {makeStyles} from '@material-ui/core/styles'
|
||||||
|
import Menu, {MenuProps} from '@material-ui/core/Menu'
|
||||||
|
import MenuItem, {MenuItemProps} from '@material-ui/core/MenuItem'
|
||||||
|
import ArrowRight from '@material-ui/icons/ArrowRight'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
|
export interface NestedMenuItemProps extends Omit<MenuItemProps, 'button'> {
|
||||||
|
/**
|
||||||
|
* Open state of parent `<Menu />`, used to close decendent menus when the
|
||||||
|
* root menu is closed.
|
||||||
|
*/
|
||||||
|
parentMenuOpen: boolean
|
||||||
|
/**
|
||||||
|
* Component for the container element.
|
||||||
|
* @default 'div'
|
||||||
|
*/
|
||||||
|
component?: React.ElementType
|
||||||
|
/**
|
||||||
|
* Effectively becomes the `children` prop passed to the `<MenuItem/>`
|
||||||
|
* element.
|
||||||
|
*/
|
||||||
|
label?: React.ReactNode
|
||||||
|
/**
|
||||||
|
* @default <ArrowRight />
|
||||||
|
*/
|
||||||
|
rightIcon?: React.ReactNode
|
||||||
|
/**
|
||||||
|
* Props passed to container element.
|
||||||
|
*/
|
||||||
|
ContainerProps?: React.HTMLAttributes<HTMLElement> &
|
||||||
|
React.RefAttributes<HTMLElement | null>
|
||||||
|
/**
|
||||||
|
* Props passed to sub `<Menu/>` element
|
||||||
|
*/
|
||||||
|
MenuProps?: Omit<MenuProps, 'children'>
|
||||||
|
/**
|
||||||
|
* @see https://material-ui.com/api/list-item/
|
||||||
|
*/
|
||||||
|
button?: true | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const TRANSPARENT = 'rgba(0,0,0,0)'
|
||||||
|
const useMenuItemStyles = makeStyles((theme) => ({
|
||||||
|
root: (props: any) => ({
|
||||||
|
backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use as a drop-in replacement for `<MenuItem>` when you need to add cascading
|
||||||
|
* menu elements as children to this component.
|
||||||
|
*/
|
||||||
|
const NestedMenuItem = React.forwardRef<
|
||||||
|
HTMLLIElement | null,
|
||||||
|
NestedMenuItemProps
|
||||||
|
>(function NestedMenuItem(props, ref) {
|
||||||
|
const {
|
||||||
|
parentMenuOpen,
|
||||||
|
component = 'div',
|
||||||
|
label,
|
||||||
|
rightIcon = <ArrowRight />,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
tabIndex: tabIndexProp,
|
||||||
|
MenuProps = {},
|
||||||
|
ContainerProps: ContainerPropsProp = {},
|
||||||
|
...MenuItemProps
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const {ref: containerRefProp, ...ContainerProps} = ContainerPropsProp
|
||||||
|
|
||||||
|
const menuItemRef = useRef<HTMLLIElement>(null)
|
||||||
|
useImperativeHandle(ref, () => menuItemRef.current)
|
||||||
|
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
useImperativeHandle(containerRefProp, () => containerRef.current)
|
||||||
|
|
||||||
|
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false)
|
||||||
|
|
||||||
|
const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setIsSubMenuOpen(true)
|
||||||
|
|
||||||
|
if (ContainerProps?.onMouseEnter) {
|
||||||
|
ContainerProps.onMouseEnter(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setIsSubMenuOpen(false)
|
||||||
|
|
||||||
|
if (ContainerProps?.onMouseLeave) {
|
||||||
|
ContainerProps.onMouseLeave(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any immediate children are active
|
||||||
|
const isSubmenuFocused = () => {
|
||||||
|
const active = containerRef.current?.ownerDocument?.activeElement
|
||||||
|
for (const child of menuContainerRef.current?.children ?? []) {
|
||||||
|
if (child === active) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
|
||||||
|
if (event.target === containerRef.current) {
|
||||||
|
setIsSubMenuOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContainerProps?.onFocus) {
|
||||||
|
ContainerProps.onFocus(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSubmenuFocused()) {
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
const active = containerRef.current?.ownerDocument?.activeElement
|
||||||
|
|
||||||
|
if (event.key === 'ArrowLeft' && isSubmenuFocused()) {
|
||||||
|
containerRef.current?.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
event.key === 'ArrowRight' &&
|
||||||
|
event.target === containerRef.current &&
|
||||||
|
event.target === active
|
||||||
|
) {
|
||||||
|
const firstChild = menuContainerRef.current?.children[0] as
|
||||||
|
| HTMLElement
|
||||||
|
| undefined
|
||||||
|
firstChild?.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = isSubMenuOpen && parentMenuOpen
|
||||||
|
const menuItemClasses = useMenuItemStyles({open})
|
||||||
|
|
||||||
|
// Root element must have a `tabIndex` attribute for keyboard navigation
|
||||||
|
let tabIndex
|
||||||
|
if (!props.disabled) {
|
||||||
|
tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...ContainerProps}
|
||||||
|
ref={containerRef}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
{...MenuItemProps}
|
||||||
|
className={clsx(menuItemClasses.root, className)}
|
||||||
|
ref={menuItemRef}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
{rightIcon}
|
||||||
|
</MenuItem>
|
||||||
|
<Menu
|
||||||
|
// Set pointer events to 'none' to prevent the invisible Popover div
|
||||||
|
// from capturing events for clicks and hovers
|
||||||
|
style={{pointerEvents: 'none'}}
|
||||||
|
anchorEl={menuItemRef.current}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right'
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'left'
|
||||||
|
}}
|
||||||
|
open={open}
|
||||||
|
autoFocus={false}
|
||||||
|
disableAutoFocus
|
||||||
|
disableEnforceFocus
|
||||||
|
onClose={() => {
|
||||||
|
setIsSubMenuOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={menuContainerRef} style={{pointerEvents: 'auto'}}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default NestedMenuItem
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
import React, { useState, useEffect, useLayoutEffect } from "react";
|
||||||
|
import theme from '../theme';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Chip,
|
||||||
|
Typography,
|
||||||
|
Paper,
|
||||||
|
Avatar,
|
||||||
|
Grid,
|
||||||
|
Tooltip,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AvatarGroup,
|
||||||
|
} from "@mui/material"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Restore as RestoreIcon,
|
||||||
|
Edit as EditIcon,
|
||||||
|
BubbleChart as BubbleChartIcon,
|
||||||
|
MoreVert as MoreVertIcon,
|
||||||
|
} from '@material-ui/icons';
|
||||||
|
|
||||||
|
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
const workflowActionStyle = {
|
||||||
|
display: "flex",
|
||||||
|
width: 160,
|
||||||
|
height: 44,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}
|
||||||
|
|
||||||
|
const paperAppStyle = {
|
||||||
|
minHeight: 130,
|
||||||
|
maxHeight: 130,
|
||||||
|
overflow: "hidden",
|
||||||
|
width: "100%",
|
||||||
|
color: "white",
|
||||||
|
backgroundColor: theme.palette.surfaceColor,
|
||||||
|
padding: "12px 12px 0px 15px",
|
||||||
|
borderRadius: 5,
|
||||||
|
display: "flex",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
position: "relative",
|
||||||
|
}
|
||||||
|
|
||||||
|
const chipStyle = {
|
||||||
|
backgroundColor: "#3d3f43",
|
||||||
|
marginRight: 5,
|
||||||
|
paddingLeft: 5,
|
||||||
|
paddingRight: 5,
|
||||||
|
height: 28,
|
||||||
|
cursor: "pointer",
|
||||||
|
borderColor: "#3d3f43",
|
||||||
|
color: "white",
|
||||||
|
}
|
||||||
|
|
||||||
|
const WorkflowPaper = (props) => {
|
||||||
|
const { data } = props;
|
||||||
|
let navigate = useNavigate();
|
||||||
|
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||||
|
const appGroup = data.action_references === undefined || data.action_references === null ? [] : data.action_references
|
||||||
|
|
||||||
|
//console.log("Workflow: ", data)
|
||||||
|
var boxColor = "#86c142";
|
||||||
|
|
||||||
|
var parsedName = data.name;
|
||||||
|
if (
|
||||||
|
parsedName !== undefined &&
|
||||||
|
parsedName !== null &&
|
||||||
|
parsedName.length > 20
|
||||||
|
) {
|
||||||
|
parsedName = parsedName.slice(0, 21) + "..";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const imageStyle = {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
marginRight: 10,
|
||||||
|
border: "1px solid rgba(255,255,255,0.3)",
|
||||||
|
}
|
||||||
|
var image = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.image !== undefined && data.creator_info.image !== null && data.creator_info.image.length > 0 ? <Avatar alt={data.creator} src={data.creator_info.image} style={imageStyle}/> : <Avatar alt={"shuffle_image"} src={theme.palette.defaultImage} style={imageStyle}/>
|
||||||
|
const creatorname = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.username !== undefined && data.creator_info.username !== null && data.creator_info.username.length > 0 ? data.creator_info.username : ""
|
||||||
|
var orgName = "";
|
||||||
|
var orgId = "";
|
||||||
|
if ((data.objectID === undefined || data.objectID === null) && data.id !== undefined && data.id !== null) {
|
||||||
|
data.objectID = data.id
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log("IMG: ", data)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{width: "100%", position: "relative",}}>
|
||||||
|
<Paper square style={paperAppStyle}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 1,
|
||||||
|
left: 1,
|
||||||
|
height: 12,
|
||||||
|
width: 12,
|
||||||
|
backgroundColor: boxColor,
|
||||||
|
borderRadius: "0 100px 0 0",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
style={{ display: "flex", flexDirection: "column", width: "100%" }}
|
||||||
|
>
|
||||||
|
<Grid item style={{ display: "flex", maxHeight: 34 }}>
|
||||||
|
<Tooltip title={`${creatorname}`} placement="bottom">
|
||||||
|
<div
|
||||||
|
style={{ cursor: data.creator_info !== undefined ? "pointer" : "inherit" }}
|
||||||
|
onClick={() => {
|
||||||
|
if (data.creator_info !== undefined) {
|
||||||
|
navigate("/creators/"+data.creator_info.username)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{image}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={`Edit ${data.name}`} placement="bottom">
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
style={{
|
||||||
|
marginBottom: 0,
|
||||||
|
paddingBottom: 0,
|
||||||
|
maxHeight: 30,
|
||||||
|
flex: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={"/workflows/" + data.objectID}
|
||||||
|
style={{ textDecoration: "none", color: "inherit" }}
|
||||||
|
>
|
||||||
|
{parsedName}
|
||||||
|
</Link>
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
<Grid item style={workflowActionStyle}>
|
||||||
|
{appGroup.length > 0 ?
|
||||||
|
<div style={{display: "flex", marginTop: 8, }}>
|
||||||
|
<AvatarGroup max={4} style={{marginLeft: 5, maxHeight: 24,}}>
|
||||||
|
{appGroup.map((app, index) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
filter: "brightness(0.6)",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
navigate("/apps/"+app.id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip color="primary" title={app.name} placement="bottom">
|
||||||
|
<Avatar alt={app.name} src={app.image_url} style={{width: 24, height: 24}}/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</AvatarGroup>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<Tooltip color="primary" title="Action amount" placement="bottom">
|
||||||
|
<span style={{ color: "#979797", display: "flex" }}>
|
||||||
|
<BubbleChartIcon
|
||||||
|
style={{ marginTop: "auto", marginBottom: "auto" }}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
marginTop: "auto",
|
||||||
|
marginBottom: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.actions === undefined || data.actions === null ? 1 : data.actions.length}
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
<Tooltip
|
||||||
|
color="primary"
|
||||||
|
title="Trigger amount"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{ marginLeft: 15, color: "#979797", display: "flex" }}
|
||||||
|
>
|
||||||
|
<RestoreIcon
|
||||||
|
style={{
|
||||||
|
color: "#979797",
|
||||||
|
marginTop: "auto",
|
||||||
|
marginBottom: "auto",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
marginTop: "auto",
|
||||||
|
marginBottom: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.triggers === undefined || data.triggers === null ? 1 : data.triggers.length}
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip color="primary" title="Subflows used" placement="bottom">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: 15,
|
||||||
|
display: "flex",
|
||||||
|
color: "#979797",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
style={{
|
||||||
|
color: "#979797",
|
||||||
|
marginTop: "auto",
|
||||||
|
marginBottom: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0H15V15H0V0ZM16 16H18V18H16V16ZM16 13H18V15H16V13ZM16 10H18V12H16V10ZM16 7H18V9H16V7ZM16 4H18V6H16V4ZM13 16H15V18H13V16ZM10 16H12V18H10V16ZM7 16H9V18H7V16ZM4 16H6V18H4V16Z"
|
||||||
|
fill="#979797"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<Typography
|
||||||
|
style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
marginTop: "auto",
|
||||||
|
marginBottom: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{0}
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
style={{
|
||||||
|
justifyContent: "left",
|
||||||
|
overflow: "hidden",
|
||||||
|
marginTop: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.tags !== undefined && data.tags !== null
|
||||||
|
? data.tags.map((tag, index) => {
|
||||||
|
if (index >= 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
key={index}
|
||||||
|
style={chipStyle}
|
||||||
|
label={tag}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkflowPaper
|
||||||
+215
-275
@@ -9,12 +9,21 @@ import theme from '../theme';
|
|||||||
import { useAlert } from "react-alert";
|
import { useAlert } from "react-alert";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Tooltip,
|
||||||
|
IconButton,
|
||||||
Typography,
|
Typography,
|
||||||
Grid,
|
Grid,
|
||||||
Paper,
|
Paper,
|
||||||
Chip,
|
Chip,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Close as CloseIcon,
|
||||||
|
DoneAll as DoneAllIcon,
|
||||||
|
} from "@material-ui/icons";
|
||||||
|
|
||||||
|
import WorkflowPaper from "../components/WorkflowPaper.jsx"
|
||||||
|
|
||||||
// core components
|
// core components
|
||||||
//import {
|
//import {
|
||||||
// chartExample1,
|
// chartExample1,
|
||||||
@@ -41,267 +50,9 @@ import {
|
|||||||
TreeMapRect,
|
TreeMapRect,
|
||||||
} from 'reaviz';
|
} from 'reaviz';
|
||||||
|
|
||||||
const categorydata = [
|
const UsecaseListComponent = ({keys, isCloud}) => {
|
||||||
{
|
const [expandedIndex, setExpandedIndex] = useState(-1);
|
||||||
"name": "1. Collect & Distribute",
|
const [expandedItem, setExpandedItem] = useState(-1);
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"name": "2-way Ticket synchronization",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Email management",
|
|
||||||
"items": {
|
|
||||||
"name": "Release a quarantined message",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "EDR to ticket",
|
|
||||||
"items": {
|
|
||||||
"name": "Get host information",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "SIEM to ticket",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ChatOps",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Threat Intel received",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Domain investigation with LetsEncrypt",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Botnet tracker",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Get running containers",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Assign tickets",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Firewall alerts",
|
|
||||||
"items": {
|
|
||||||
"name": "URL filtering",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "IDS/IPS alerts",
|
|
||||||
"items": {
|
|
||||||
"name": "Manage policies",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Deduplicate information",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Correlate information",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "3. Detect",
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"name": "Search SIEM (Sigma)",
|
|
||||||
"items": {
|
|
||||||
"name": "Endpoint",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Search EDR (OSQuery)",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Search emails (Phish)",
|
|
||||||
"items": {
|
|
||||||
"name": "Check headers and IOCs",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Search IOCs (ioc-finder)",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Search files (Yara)",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Correlate tickets",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Honeypot access",
|
|
||||||
"items": {
|
|
||||||
"name": "...",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Verify",
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"name": "Discover vulnerabilities",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Discover assets",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Ensure policies are followed",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Find Inactive users",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Ensure access rights match HR systems",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Ensure onboarding is followed",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Third party apps in SaaS",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Devices used for your cloud account",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Too much access in GCP/Azure/AWS/ other clouds",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Certificate validation",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Monitor new DNS entries for domain with passive DNS",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Monitor and track password dumps",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Monitor for mentions of domain on darknet sites",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Reporting",
|
|
||||||
"items": {
|
|
||||||
"name": "Monthly reports",
|
|
||||||
"items": {
|
|
||||||
"name": "...",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "4. Respond",
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"name": "Eradicate malware",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Quarantine host(s)",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Trigger scans",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Update indicators (FW, EDR, SIEM...)",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Autoblock activity when threat intel is received",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Lock/Delete/Reset account",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Lock vault",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Increase authentication",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Trigger scans",
|
|
||||||
"items": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Get policies from assets",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "2. Enrich",
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"name": "Internal Enrichment",
|
|
||||||
"items": {
|
|
||||||
"name": "...",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "External historical Enrichment",
|
|
||||||
"items": {
|
|
||||||
"name": "...",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Realtime",
|
|
||||||
"items": {
|
|
||||||
"name": "Analyze screenshots",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Ticketing webhook verification",
|
|
||||||
"items": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const UsecaseListComponent = ({keys}) => {
|
|
||||||
if (keys === undefined || keys === null || keys.length === 0) {
|
if (keys === undefined || keys === null || keys.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -322,14 +73,112 @@ const UsecaseListComponent = ({keys}) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={3} style={{marginTop: 25}}>
|
<Grid container spacing={3} style={{marginTop: 25}}>
|
||||||
{usecase.list.map((subcase, subindex) => {
|
{usecase.list.map((subcase, subindex) => {
|
||||||
|
if (subcase.matches === undefined || subcase.matches === null) {
|
||||||
|
subcase.matches = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedItem = subindex === expandedItem && index === expandedIndex
|
||||||
|
const finished = subcase.matches.length > 0
|
||||||
|
//const backgroundColor = selectedItem ? "inherit" : finished ? "inherit" : usecase.color
|
||||||
|
const backgroundColor = "inherit"
|
||||||
|
const itemBorder = `${selectedItem ? "3px" : expandedItem >= 0 ? "0px" : "1px"} solid ${usecase.color}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid item xs={4} key={subindex} style={{minHeight: 110,}}>
|
<Grid item xs={selectedItem ? 12 : 4} key={subindex} style={{minHeight: 110,}} onClick={() => {
|
||||||
<Paper style={{padding: "30px 30px 20px 30px", minHeight: 110, cursor: "pointer", border: `1px solid ${usecase.color}`}} onClick={() => {
|
if (selectedItem) {
|
||||||
console.log("Clicked: ", subcase.name)
|
} else {
|
||||||
|
setExpandedIndex(index)
|
||||||
|
setExpandedItem(subindex)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<Paper style={{padding: "30px 30px 30px 30px", minHeight: 75, cursor: !selectedItem ? "pointer" : "default", border: itemBorder, backgroundColor: backgroundColor,}} onClick={() => {
|
||||||
|
console.log("Clicked: ", subcase)
|
||||||
}}>
|
}}>
|
||||||
<Typography variant="body1">
|
{!selectedItem ?
|
||||||
<b>{subcase.name}</b>
|
<div style={{textAlign: "left", position: "relative",}}>
|
||||||
</Typography>
|
<Typography variant="h6">
|
||||||
|
<b>{subcase.name}</b>
|
||||||
|
</Typography>
|
||||||
|
{finished ?
|
||||||
|
<Tooltip
|
||||||
|
title="A workflow has been assigned"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
style={{ position: "absolute", top: -20, right: -20}}
|
||||||
|
onClick={(e) => {
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DoneAllIcon style={{ color: usecase.color }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div style={{textAlign: "left", position: "relative",}}>
|
||||||
|
<Typography variant="h6">
|
||||||
|
<b>{subcase.name}</b>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Description: {subcase.description}
|
||||||
|
</Typography>
|
||||||
|
<Tooltip
|
||||||
|
title="Close window"
|
||||||
|
placement="top"
|
||||||
|
style={{ zIndex: 10011 }}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
style={{ position: "absolute", top: 0, right: 0}}
|
||||||
|
onClick={(e) => {
|
||||||
|
setExpandedItem(-1)
|
||||||
|
setExpandedIndex(-1)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon style={{ color: "white" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<div style={{marginTop: 25, display: "flex", minHeight: 400, maxHeight: 400, }}>
|
||||||
|
<img
|
||||||
|
alt={subcase.name}
|
||||||
|
src={"/images/detectionframework.png"}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: 400,
|
||||||
|
width: 400,
|
||||||
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
border: "1px solid rgba(255,255,255,0.3)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{flex: 1, marginLeft: 10, textAlign: "center",}}>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Your workflow{subcase.matches.length === 1 ? "" : "s"} ({subcase.matches.length})
|
||||||
|
</Typography>
|
||||||
|
{subcase.matches.length > 0 ?
|
||||||
|
<Grid container xs={3} style={{maxWidth: 325, margin: "auto", marginTop: 10, itemAlign: "center", }}>
|
||||||
|
{subcase.matches.map((workflow, workflowindex) => {
|
||||||
|
return (
|
||||||
|
<Grid index={workflowindex} xs={12}>
|
||||||
|
<WorkflowPaper key={workflowindex} data={workflow} />
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
:
|
||||||
|
<div>
|
||||||
|
<Typography variant="body1" color="textSecondary">
|
||||||
|
No workflow selected yet.
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{isCloud !== false ?
|
||||||
|
<Typography variant="h6">
|
||||||
|
Public workflows
|
||||||
|
</Typography>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
)
|
)
|
||||||
@@ -403,7 +252,6 @@ const RadialChart = ({keys, setSelectedCategory}) => {
|
|||||||
<RadialAreaSeries
|
<RadialAreaSeries
|
||||||
interpolation="smooth"
|
interpolation="smooth"
|
||||||
colorScheme={(colorInput) => {
|
colorScheme={(colorInput) => {
|
||||||
console.log("Color: ", colorInput)
|
|
||||||
return '#f86a3e'
|
return '#f86a3e'
|
||||||
}}
|
}}
|
||||||
animated={false}
|
animated={false}
|
||||||
@@ -473,7 +321,7 @@ const RadialChart = ({keys, setSelectedCategory}) => {
|
|||||||
// This is the start of a dashboard that can be used.
|
// This is the start of a dashboard that can be used.
|
||||||
// What data do we fill in here? Idk
|
// What data do we fill in here? Idk
|
||||||
const Dashboard = (props) => {
|
const Dashboard = (props) => {
|
||||||
const { globalUrl } = props;
|
const { globalUrl, isLoggedIn } = props;
|
||||||
const alert = useAlert();
|
const alert = useAlert();
|
||||||
const [bigChartData, setBgChartData] = useState("data1");
|
const [bigChartData, setBgChartData] = useState("data1");
|
||||||
const [dayAmount, setDayAmount] = useState(7);
|
const [dayAmount, setDayAmount] = useState(7);
|
||||||
@@ -487,6 +335,44 @@ const Dashboard = (props) => {
|
|||||||
const [selectedUsecaseCategory, setSelectedUsecaseCategory] = useState("");
|
const [selectedUsecaseCategory, setSelectedUsecaseCategory] = useState("");
|
||||||
const [selectedUsecases, setSelectedUsecases] = useState([]);
|
const [selectedUsecases, setSelectedUsecases] = useState([]);
|
||||||
const [usecases, setUsecases] = useState([]);
|
const [usecases, setUsecases] = useState([]);
|
||||||
|
const [workflows, setWorkflows] = useState([]);
|
||||||
|
|
||||||
|
const isCloud =
|
||||||
|
window.location.host === "localhost:3002" ||
|
||||||
|
window.location.host === "shuffler.io";
|
||||||
|
|
||||||
|
const getAvailableWorkflows = () => {
|
||||||
|
fetch(globalUrl + "/api/v1/workflows", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
fetchUsecases()
|
||||||
|
console.log("Status not 200 for workflows :O!: ", response.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
fetchUsecases(responseJson)
|
||||||
|
|
||||||
|
console.log("Resp: ", responseJson)
|
||||||
|
if (responseJson !== undefined) {
|
||||||
|
//setWorkflows(responseJson);
|
||||||
|
//fetchUsecases(responseJson)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
fetchUsecases()
|
||||||
|
//alert.error(error.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("Changed: ", selectedUsecaseCategory)
|
console.log("Changed: ", selectedUsecaseCategory)
|
||||||
@@ -521,7 +407,7 @@ const Dashboard = (props) => {
|
|||||||
setTreeKeys(treeCategories)
|
setTreeKeys(treeCategories)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchUsecases = () => {
|
const fetchUsecases = (workflows) => {
|
||||||
fetch(globalUrl + "/api/v1/workflows/usecases", {
|
fetch(globalUrl + "/api/v1/workflows/usecases", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -540,9 +426,62 @@ const Dashboard = (props) => {
|
|||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (responseJson.success !== false) {
|
if (responseJson.success !== false) {
|
||||||
console.log("Usecases: ", responseJson)
|
console.log("Usecases: ", responseJson)
|
||||||
handleKeysetting(responseJson)
|
if (workflows !== undefined && workflows !== null && workflows.length > 0) {
|
||||||
setUsecases(responseJson)
|
console.log("Got workflows: ", workflows)
|
||||||
setSelectedUsecases(responseJson)
|
var categorydata = responseJson
|
||||||
|
|
||||||
|
var newcategories = []
|
||||||
|
for (var key in categorydata) {
|
||||||
|
var category = categorydata[key]
|
||||||
|
category.matches = []
|
||||||
|
|
||||||
|
for (var subcategorykey in category.list) {
|
||||||
|
var subcategory = category.list[subcategorykey]
|
||||||
|
subcategory.matches = []
|
||||||
|
|
||||||
|
for (var workflowkey in workflows) {
|
||||||
|
const workflow = workflows[workflowkey]
|
||||||
|
|
||||||
|
if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) {
|
||||||
|
for (var usecasekey in workflow.usecase_ids) {
|
||||||
|
if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) {
|
||||||
|
console.log("Got match: ", workflow.usecase_ids[usecasekey])
|
||||||
|
|
||||||
|
category.matches.push({
|
||||||
|
"workflow": workflow.id,
|
||||||
|
"category": subcategory.name,
|
||||||
|
})
|
||||||
|
|
||||||
|
subcategory.matches.push(workflow)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcategory.matches.length > 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newcategories.push(category)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Categories: ", newcategories)
|
||||||
|
if (newcategories !== undefined && newcategories !== null && newcategories.length > 0) {
|
||||||
|
handleKeysetting(newcategories)
|
||||||
|
setUsecases(newcategories)
|
||||||
|
setSelectedUsecases(newcategories)
|
||||||
|
} else {
|
||||||
|
handleKeysetting(responseJson)
|
||||||
|
setUsecases(responseJson)
|
||||||
|
setSelectedUsecases(responseJson)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleKeysetting(responseJson)
|
||||||
|
setUsecases(responseJson)
|
||||||
|
setSelectedUsecases(responseJson)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -552,7 +491,8 @@ const Dashboard = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUsecases()
|
getAvailableWorkflows()
|
||||||
|
//fetchUsecases()
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchdata = (stats_id) => {
|
const fetchdata = (stats_id) => {
|
||||||
@@ -845,7 +785,7 @@ const Dashboard = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
<UsecaseListComponent keys={selectedUsecases} />
|
<UsecaseListComponent keys={selectedUsecases} isCloud={isCloud} />
|
||||||
|
|
||||||
{treeKeys.length > 0 ?
|
{treeKeys.length > 0 ?
|
||||||
<TreeChart keys={treeKeys} />
|
<TreeChart keys={treeKeys} />
|
||||||
|
|||||||
@@ -930,7 +930,7 @@ const Workflows = (props) => {
|
|||||||
//workflows[0].usecase_ids = ["Correlate tickets"]
|
//workflows[0].usecase_ids = ["Correlate tickets"]
|
||||||
|
|
||||||
if (workflows !== undefined && workflows !== null) {
|
if (workflows !== undefined && workflows !== null) {
|
||||||
const newcategories = []
|
var newcategories = []
|
||||||
for (var key in categorydata) {
|
for (var key in categorydata) {
|
||||||
var category = categorydata[key]
|
var category = categorydata[key]
|
||||||
category.matches = []
|
category.matches = []
|
||||||
@@ -2493,7 +2493,7 @@ const Workflows = (props) => {
|
|||||||
<em>None</em>
|
<em>None</em>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{usecases.map((usecase, index) => {
|
{usecases.map((usecase, index) => {
|
||||||
console.log(usecase)
|
//console.log(usecase)
|
||||||
return (
|
return (
|
||||||
<span key={index}>
|
<span key={index}>
|
||||||
<ListSubheader
|
<ListSubheader
|
||||||
@@ -2714,7 +2714,7 @@ const Workflows = (props) => {
|
|||||||
{isCloud ? null : (
|
{isCloud ? null : (
|
||||||
<Tooltip color="primary" title={"Import workflows to Shuffle"} placement="top">
|
<Tooltip color="primary" title={"Import workflows to Shuffle"} placement="top">
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="secondary"
|
||||||
style={{}}
|
style={{}}
|
||||||
variant="text"
|
variant="text"
|
||||||
onClick={() => setLoadWorkflowsModalOpen(true)}
|
onClick={() => setLoadWorkflowsModalOpen(true)}
|
||||||
@@ -2985,7 +2985,7 @@ const Workflows = (props) => {
|
|||||||
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
|
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
|
||||||
<div style={{ display: "flex",}}>
|
<div style={{ display: "flex",}}>
|
||||||
{usecases.map((usecase, index) => {
|
{usecases.map((usecase, index) => {
|
||||||
console.log(usecase)
|
//console.log(usecase)
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
key={usecase.name}
|
key={usecase.name}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
NAME=shuffle-orborus
|
NAME=shuffle-orborus
|
||||||
VERSION=0.9.59
|
VERSION=0.9.61
|
||||||
|
|
||||||
echo "Running docker build with $NAME:$VERSION"
|
echo "Running docker build with $NAME:$VERSION"
|
||||||
#docker rmi frikky/shuffle:$NAME --force
|
#docker rmi frikky/shuffle:$NAME --force
|
||||||
|
|||||||
Reference in New Issue
Block a user