mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
eventing updates
This commit is contained in:
@@ -26,6 +26,39 @@ import {MythicDialogButton, MythicDialogFooter} from "./MythicDialogLayout";
|
||||
let mythicDialogIdCounter = 0;
|
||||
let mythicDialogStack = [];
|
||||
|
||||
const MythicDraggableDialogPaper = React.forwardRef(function MythicDraggableDialogPaper(props, ref) {
|
||||
const {
|
||||
draggableCancel,
|
||||
draggableHandle,
|
||||
draggableOnStart,
|
||||
draggableOnStop,
|
||||
...paperProps
|
||||
} = props;
|
||||
const nodeRef = React.useRef(null);
|
||||
const setPaperRef = React.useCallback((node) => {
|
||||
nodeRef.current = node;
|
||||
if(typeof ref === "function"){
|
||||
ref(node);
|
||||
} else if(ref){
|
||||
ref.current = node;
|
||||
}
|
||||
}, [ref]);
|
||||
const onStart = React.useCallback((event, data) => {
|
||||
return draggableOnStart?.(event, data, nodeRef.current);
|
||||
}, [draggableOnStart]);
|
||||
return (
|
||||
<Draggable
|
||||
nodeRef={nodeRef}
|
||||
handle={draggableHandle}
|
||||
cancel={draggableCancel}
|
||||
onStart={onStart}
|
||||
onStop={draggableOnStop}
|
||||
>
|
||||
<Paper {...paperProps} ref={setPaperRef} />
|
||||
</Draggable>
|
||||
);
|
||||
});
|
||||
|
||||
export function MythicDialog(props) {
|
||||
const theme = useTheme();
|
||||
const dialogIdRef = React.useRef(null);
|
||||
@@ -45,7 +78,6 @@ export function MythicDialog(props) {
|
||||
hideBackdrop: false,
|
||||
modified: false
|
||||
});
|
||||
const nodeRef = React.useRef(null);
|
||||
const descriptionElementRef = React.useRef(null);
|
||||
React.useEffect(() => {
|
||||
if(!props.open){
|
||||
@@ -94,55 +126,52 @@ export function MythicDialog(props) {
|
||||
}
|
||||
props.onClose?.();
|
||||
}
|
||||
const onStart = (e) => {
|
||||
const onStart = React.useCallback((e, _data, paperNode) => {
|
||||
if(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
if(!draggedState.modified){
|
||||
setDraggedState({
|
||||
style: {
|
||||
height: e.target.offsetParent.offsetHeight + "px",
|
||||
width: e.target.offsetParent.offsetWidth + "px",
|
||||
margin: "auto",
|
||||
overflowY: "auto",
|
||||
},
|
||||
setDraggedState((currentState) => {
|
||||
if(currentState.modified){
|
||||
return currentState;
|
||||
}
|
||||
const dragTarget = paperNode || e?.target?.closest?.(".MuiPaper-root") || e?.target?.offsetParent;
|
||||
const targetWidth = dragTarget?.offsetWidth || e?.target?.offsetParent?.offsetWidth || 0;
|
||||
return {
|
||||
style: {},
|
||||
paperStyle: {
|
||||
//height: e.target.offsetParent.offsetHeight + "px",
|
||||
width: e.target.offsetParent.offsetWidth + "px",
|
||||
...(targetWidth > 0 ? {width: targetWidth + "px"} : {}),
|
||||
margin: 0,
|
||||
overflowY: "auto",
|
||||
},
|
||||
containerStyle: {
|
||||
height: "fit-content",
|
||||
overflowY: "auto"
|
||||
overflow: "visible",
|
||||
},
|
||||
hideBackdrop: true,
|
||||
modified: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
const onStop = (e) => {
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
const onStop = React.useCallback((e) => {
|
||||
if(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
<Draggable
|
||||
nodeRef={nodeRef}
|
||||
handle="#mythic-draggable-title"
|
||||
cancel={'[class*="MuiDialogContent-root"]'}
|
||||
onStart={onStart}
|
||||
onStop={onStop}
|
||||
>
|
||||
<Dialog
|
||||
ref={nodeRef}
|
||||
open={props.open}
|
||||
onClose={handleOnClose}
|
||||
scroll="paper"
|
||||
maxWidth={props.maxWidth}
|
||||
fullWidth={true}
|
||||
PaperComponent={MythicDraggableDialogPaper}
|
||||
PaperProps={{
|
||||
draggableHandle: "#mythic-draggable-title",
|
||||
draggableCancel: '[class*="MuiDialogContent-root"]',
|
||||
draggableOnStart: onStart,
|
||||
draggableOnStop: onStop,
|
||||
}}
|
||||
style={{...props.style, ...draggedState.style}}
|
||||
disableEnforceFocus={true}
|
||||
disablePortal={false}
|
||||
@@ -165,7 +194,6 @@ export function MythicDialog(props) {
|
||||
>
|
||||
{props.innerDialog}
|
||||
</Dialog>
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {copyStringToClipboard} from "../../utilities/Clipboard";
|
||||
import {ipCompare} from "../Callbacks/CallbacksTable";
|
||||
import MythicResizableGrid from "../../MythicComponents/MythicResizableGrid";
|
||||
import {CallbacksTableStringCell} from "../Callbacks/CallbacksTableRow";
|
||||
import {GetComputedFontSize} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
|
||||
const cancelEventGroupInstanceMutation = gql(`
|
||||
mutation cancelEventGroupInstanceMutation($eventgroupinstance_id: Int!){
|
||||
@@ -79,6 +80,7 @@ export const adjustOutput = (e, newTime) => {
|
||||
|
||||
function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, setSelectedInstance, selectedInstanceID}){
|
||||
const callbackTableGridRef = React.useRef();
|
||||
const eventingRowHeight = Math.max(GetComputedFontSize() + 22, 42);
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = React.useState(false);
|
||||
const [openRetryDialog, setOpenRetryDialog] = React.useState(false);
|
||||
const [openRunAgainDialog, setOpenRunAgainDialog] = React.useState(false);
|
||||
@@ -311,13 +313,13 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
return <CallbacksTableStringCell rowData={row} cellData={row.trigger} />
|
||||
case "Time":
|
||||
return (
|
||||
<div>
|
||||
<div style={{display: "flex", flexDirection: "row", alignItems: "center"}}>
|
||||
<CalendarMonthTwoToneIcon style={{marginRight: "10px"}}/>
|
||||
<div className="mythic-eventing-instances-time-cell">
|
||||
<div className="mythic-eventing-instances-time-line">
|
||||
<CalendarMonthTwoToneIcon fontSize="small" />
|
||||
{toLocalTime(row?.created_at, me?.user?.view_utc_time)}
|
||||
</div>
|
||||
<div style={{display: "flex", flexDirection: "row", alignItems: "center"}}>
|
||||
<AccessAlarmTwoToneIcon style={{marginRight: "10px"}}/>
|
||||
<div className="mythic-eventing-instances-time-line mythic-eventing-instances-time-secondary">
|
||||
<AccessAlarmTwoToneIcon fontSize="small" />
|
||||
{row.end_timestamp === null &&
|
||||
<Moment filter={(newTime) => adjustOutput(row, newTime)} interval={1000}
|
||||
parse={"YYYY-MM-DDTHH:mm:ss.SSSSSSZ"}
|
||||
@@ -400,15 +402,15 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
return [];
|
||||
}
|
||||
return (
|
||||
<div style={{ width: '100%', overflow: "auto", flexGrow: 1, display: "flex", position: "relative"}}>
|
||||
<div className="mythic-eventing-instances-grid">
|
||||
<MythicResizableGrid
|
||||
name={"eventing_instances_table"}
|
||||
callbackTableGridRef={callbackTableGridRef}
|
||||
columns={columns}
|
||||
sortIndicatorIndex={sortColumn}
|
||||
sortDirection={sortData.sortDirection}
|
||||
items={sortedData}
|
||||
rowHeight={40}
|
||||
headerRowHeight={20}
|
||||
rowHeight={eventingRowHeight}
|
||||
onClickHeader={onClickHeader}
|
||||
onDoubleClickRow={onRowDoubleClick}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
|
||||
@@ -14,15 +14,7 @@ import {
|
||||
EventStepRenderFlowWithProvider
|
||||
} from "./EventStepRender";
|
||||
import OpenInNewTwoToneIcon from '@mui/icons-material/OpenInNewTwoTone';
|
||||
import AddCircleIcon from '@mui/icons-material/AddCircle';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import MythicTableCell from "../../MythicComponents/MythicTableCell";
|
||||
import ContentCopyTwoToneIcon from '@mui/icons-material/ContentCopyTwoTone';
|
||||
import {EventGroupInstances} from "./EventGroupInstances";
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import {MythicConfirmDialog} from '../../MythicComponents/MythicConfirmDialog';
|
||||
@@ -40,8 +32,9 @@ import LayersTwoToneIcon from '@mui/icons-material/LayersTwoTone';
|
||||
import {EventGroupConsumingContainersDialog} from "./EventGroupConsumingContainersDialog";
|
||||
import CalendarMonthTwoToneIcon from '@mui/icons-material/CalendarMonthTwoTone';
|
||||
import {EventGroupTableEditDialog} from "./EventEditEventGroupDialog";
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import EditNoteTwoToneIcon from '@mui/icons-material/EditNoteTwoTone';
|
||||
import {MythicPageHeader, MythicPageHeaderChip} from "../../MythicComponents/MythicPageHeader";
|
||||
import {MythicStateChip} from "../../MythicComponents/MythicStateChip";
|
||||
|
||||
const updateDeleteStatusMutation = gql(`
|
||||
mutation updateDeleteStatusMutation($eventgroup_id: Int!, $deleted: Boolean!) {
|
||||
@@ -151,49 +144,6 @@ export function EventGroupTable({selectedEventGroup, me, showInstances, showGrap
|
||||
}
|
||||
{selectedEventGroup.id !== 0 &&
|
||||
<>
|
||||
<MythicPageHeader
|
||||
dense
|
||||
title={selectedEventGroup?.name}
|
||||
subtitle={selectedEventGroup?.description}
|
||||
meta={
|
||||
<>
|
||||
<MythicPageHeaderChip
|
||||
icon={selectedEventGroup.active ? <NotificationsActiveTwoToneIcon /> : <NotificationsOffTwoToneIcon />}
|
||||
label={selectedEventGroup.active ? "Enabled" : "Disabled"}
|
||||
status={selectedEventGroup.active ? "enabled" : "disabled"}
|
||||
/>
|
||||
<MythicPageHeaderChip label={selectedEventGroup.trigger} />
|
||||
<MythicPageHeaderChip label={selectedEventGroup.run_as || "unknown"} />
|
||||
<MythicPageHeaderChip
|
||||
label={selectedEventGroup.approved_to_run ? "Approved" : "Needs approval"}
|
||||
status={selectedEventGroup.approved_to_run ? "success" : "warning"}
|
||||
/>
|
||||
{selectedEventGroup.deleted && <MythicPageHeaderChip label="Deleted" status="error" />}
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
{selectedEventGroup.deleted ? (
|
||||
<Button className="mythic-table-row-action-hover-success" variant="outlined" size="small" startIcon={<RestoreFromTrashIcon fontSize="small" />} onClick={onAcceptDelete}>
|
||||
Restore
|
||||
</Button>
|
||||
) : (
|
||||
<Button className="mythic-table-row-action-hover-danger" variant="outlined" size="small" startIcon={<DeleteIcon fontSize="small" />} onClick={() => setOpenDeleteDialog(true)}>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
{selectedEventGroup.active ? (
|
||||
<Button className="mythic-table-row-action-hover-warning" variant="outlined" size="small" startIcon={<NotificationsActiveTwoToneIcon fontSize="small" />} onClick={() => setOpenActiveDialog(true)}>
|
||||
Disable
|
||||
</Button>
|
||||
) : (
|
||||
<Button className="mythic-table-row-action-hover-success" variant="outlined" size="small" startIcon={<NotificationsOffTwoToneIcon fontSize="small" />} onClick={onAcceptActive}>
|
||||
Enable
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{openDeleteDialog &&
|
||||
<MythicConfirmDialog onClose={() => {
|
||||
setOpenDeleteDialog(false);
|
||||
@@ -206,142 +156,25 @@ export function EventGroupTable({selectedEventGroup, me, showInstances, showGrap
|
||||
acceptText={"Disable"}
|
||||
/>
|
||||
}
|
||||
<TableContainer className="mythicElement">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Created</TableCell>
|
||||
<TableCell style={{width: "10rem"}}>Trigger</TableCell>
|
||||
<TableCell>Keywords</TableCell>
|
||||
<TableCell style={{width: "4rem"}}>Context</TableCell>
|
||||
<TableCell style={{width: "3rem"}}>Env</TableCell>
|
||||
<TableCell style={{width: "10rem"}}>Run as</TableCell>
|
||||
<TableCell style={{width: "15rem"}}>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<MythicTableCell>{selectedEventGroup?.operator?.username} @ {toLocalTime(selectedEventGroup?.created_at, me?.user?.view_utc_time)}</MythicTableCell>
|
||||
<MythicTableCell>{selectedEventGroup.trigger}
|
||||
{selectedEventGroup.trigger === "cron" &&
|
||||
<div style={{display: "flex", flexDirection: "row", alignItems: "center"}}>
|
||||
<CalendarMonthTwoToneIcon style={{marginRight: "10px"}}/>
|
||||
{toLocalTime(selectedEventGroup.next_scheduled_run, me?.user?.view_utc_time)}
|
||||
</div>
|
||||
}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell
|
||||
style={{breakWord: "break-all"}}>{selectedEventGroup.keywords.length > 0 && (
|
||||
<>{""}{selectedEventGroup.keywords?.join(", ")}</>
|
||||
)}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell >
|
||||
{selectedEventGroup.trigger_data &&
|
||||
<IconButton onClick={() => {
|
||||
setOpenTriggerDataView(true);
|
||||
}} className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small">
|
||||
<InfoTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{selectedEventGroup.environment &&
|
||||
<IconButton onClick={() => {
|
||||
setOpenEnvView(true);
|
||||
}} className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small">
|
||||
<InfoTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
<div style={{display: "flex", alignItems: "center"}}>
|
||||
{selectedEventGroup.approved_to_run ?
|
||||
(
|
||||
<MythicStyledTooltip title={"all user approvals received"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-success" size="small" onClick={() => setOpenApprovalDialog(true)}>
|
||||
<ChecklistRtlTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
|
||||
</MythicStyledTooltip>
|
||||
|
||||
) : (
|
||||
<MythicStyledTooltip title={"missing user approvals"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-danger" size="small" onClick={() => setOpenApprovalDialog(true)}>
|
||||
<RuleTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
|
||||
</MythicStyledTooltip>
|
||||
|
||||
)
|
||||
}
|
||||
{selectedEventGroup.run_as}
|
||||
</div>
|
||||
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
<div className="mythic-table-row-actions mythic-table-row-actions-nowrap">
|
||||
<MythicStyledTooltip title={"Edit Workflow Metadata (not steps)"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => {setOpenEditDialog(true)}}>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
{selectedEventGroup.trigger === "manual" &&
|
||||
<MythicStyledTooltip title={"Trigger manually now"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-success" size="small" onClick={onTriggerManual}>
|
||||
<PlayCircleFilledTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
<MythicStyledTooltip title={"Large Graph View"}>
|
||||
<IconButton onClick={() => {
|
||||
setOpenEventStepRender(true);
|
||||
}} className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small">
|
||||
<OpenInNewTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Create New Workflow From This One"}>
|
||||
<IconButton onClick={() => {
|
||||
setOpenFileView(true);
|
||||
}} className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-success" size="small">
|
||||
<AddCircleIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Manage Associated Files"}>
|
||||
<IconButton onClick={() => {
|
||||
setOpenFileManageView(true);
|
||||
}} className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small">
|
||||
<Badge badgeContent={selectedEventGroup.filemeta.length} color="secondary">
|
||||
<AttachFileIcon fontSize="small" />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
{selectedEventGroup.keywords.length > 0 &&
|
||||
<MythicStyledTooltip title={"Manually execute via keyword"}>
|
||||
<IconButton onClick={() => {
|
||||
setOpenTriggerKeyword(true);
|
||||
}} className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-success" size="small">
|
||||
<SpellcheckIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
{selectedEventGroup.eventgroupconsumingcontainers.length > 0 &&
|
||||
<MythicStyledTooltip title={"View details about associated eventing containers"} >
|
||||
<Badge badgeContent={consumingContainersErrors} color={"error"}>
|
||||
<IconButton onClick={() => {
|
||||
setOpenConsumingContainerDialog(true);
|
||||
}} className={`mythic-table-row-icon-action ${consumingContainersErrors > 0 ? "mythic-table-row-icon-action-danger" : "mythic-table-row-icon-action-hover-info"}`} size="small">
|
||||
<LayersTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Badge>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
</div>
|
||||
|
||||
</MythicTableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<EventGroupWorkflowOverview
|
||||
consumingContainersErrors={consumingContainersErrors}
|
||||
me={me}
|
||||
onClone={() => setOpenFileView(true)}
|
||||
onDelete={() => setOpenDeleteDialog(true)}
|
||||
onDisable={() => setOpenActiveDialog(true)}
|
||||
onEdit={() => setOpenEditDialog(true)}
|
||||
onEnable={onAcceptActive}
|
||||
onManageFiles={() => setOpenFileManageView(true)}
|
||||
onOpenApproval={() => setOpenApprovalDialog(true)}
|
||||
onOpenContainers={() => setOpenConsumingContainerDialog(true)}
|
||||
onOpenEnvironment={() => setOpenEnvView(true)}
|
||||
onOpenGraph={() => setOpenEventStepRender(true)}
|
||||
onOpenKeywordTrigger={() => setOpenTriggerKeyword(true)}
|
||||
onOpenTriggerData={() => setOpenTriggerDataView(true)}
|
||||
onRestore={onAcceptDelete}
|
||||
onTriggerManual={onTriggerManual}
|
||||
selectedEventGroup={selectedEventGroup}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{showGraph && <RenderSteps selectedInstanceID={selectedInstanceID} selectedEventGroup={selectedEventGroup} />}
|
||||
@@ -449,6 +282,256 @@ export function EventGroupTable({selectedEventGroup, me, showInstances, showGrap
|
||||
)
|
||||
}
|
||||
|
||||
function EventGroupWorkflowOverview({
|
||||
consumingContainersErrors,
|
||||
me,
|
||||
onClone,
|
||||
onDelete,
|
||||
onDisable,
|
||||
onEdit,
|
||||
onEnable,
|
||||
onManageFiles,
|
||||
onOpenApproval,
|
||||
onOpenContainers,
|
||||
onOpenEnvironment,
|
||||
onOpenGraph,
|
||||
onOpenKeywordTrigger,
|
||||
onOpenTriggerData,
|
||||
onRestore,
|
||||
onTriggerManual,
|
||||
selectedEventGroup,
|
||||
}) {
|
||||
const keywords = selectedEventGroup?.keywords || [];
|
||||
const visibleKeywords = keywords.slice(0, 6);
|
||||
const hiddenKeywordCount = Math.max(keywords.length - visibleKeywords.length, 0);
|
||||
const fileCount = selectedEventGroup?.filemeta?.length || 0;
|
||||
const consumingContainers = selectedEventGroup?.eventgroupconsumingcontainers || [];
|
||||
const hasTriggerData = hasWorkflowDetailValue(selectedEventGroup?.trigger_data);
|
||||
const hasEnvironment = hasWorkflowDetailValue(selectedEventGroup?.environment);
|
||||
const isApproved = Boolean(selectedEventGroup?.approved_to_run);
|
||||
const createdBy = selectedEventGroup?.operator?.username || "unknown";
|
||||
const createdAt = toLocalTime(selectedEventGroup?.created_at, me?.user?.view_utc_time);
|
||||
|
||||
return (
|
||||
<div className="mythic-eventing-workflow-overview">
|
||||
<div className="mythic-eventing-workflow-overview-header">
|
||||
<div className="mythic-eventing-workflow-overview-title-block">
|
||||
<div className="mythic-eventing-workflow-overview-title-row">
|
||||
<span className="mythic-eventing-workflow-overview-title">{selectedEventGroup?.name}</span>
|
||||
<MythicPageHeaderChip
|
||||
icon={selectedEventGroup?.active ? <NotificationsActiveTwoToneIcon /> : <NotificationsOffTwoToneIcon />}
|
||||
label={selectedEventGroup?.active ? "Enabled" : "Disabled"}
|
||||
status={selectedEventGroup?.active ? "enabled" : "disabled"}
|
||||
/>
|
||||
{selectedEventGroup?.deleted && <MythicPageHeaderChip label="Deleted" status="error" />}
|
||||
</div>
|
||||
{selectedEventGroup?.description &&
|
||||
<div className="mythic-eventing-workflow-overview-description">{selectedEventGroup.description}</div>
|
||||
}
|
||||
</div>
|
||||
<div className="mythic-eventing-workflow-overview-header-actions">
|
||||
{selectedEventGroup?.deleted ? (
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-success" variant="outlined" size="small" startIcon={<RestoreFromTrashIcon fontSize="small" />} onClick={onRestore}>
|
||||
Restore
|
||||
</Button>
|
||||
) : (
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-danger" variant="outlined" size="small" startIcon={<DeleteIcon fontSize="small" />} onClick={onDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
{selectedEventGroup?.active ? (
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-warning" variant="outlined" size="small" startIcon={<NotificationsActiveTwoToneIcon fontSize="small" />} onClick={onDisable}>
|
||||
Disable
|
||||
</Button>
|
||||
) : (
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-success" variant="outlined" size="small" startIcon={<NotificationsOffTwoToneIcon fontSize="small" />} onClick={onEnable}>
|
||||
Enable
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mythic-eventing-workflow-overview-section mythic-eventing-workflow-overview-primary">
|
||||
<div className="mythic-eventing-workflow-overview-field">
|
||||
<span className="mythic-eventing-workflow-overview-label">Created by</span>
|
||||
<span className="mythic-eventing-workflow-overview-value">{createdBy}</span>
|
||||
<span className="mythic-eventing-workflow-overview-subvalue">{createdAt}</span>
|
||||
</div>
|
||||
<div className="mythic-eventing-workflow-overview-field">
|
||||
<span className="mythic-eventing-workflow-overview-label">Trigger behavior</span>
|
||||
<div className="mythic-eventing-workflow-chip-row">
|
||||
<MythicStateChip compact label={selectedEventGroup?.trigger || "unknown"} state="info" />
|
||||
</div>
|
||||
{selectedEventGroup?.trigger === "cron" &&
|
||||
<span className="mythic-eventing-workflow-overview-subvalue mythic-eventing-workflow-overview-icon-line">
|
||||
<CalendarMonthTwoToneIcon fontSize="small" />
|
||||
{toLocalTime(selectedEventGroup?.next_scheduled_run, me?.user?.view_utc_time)}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mythic-eventing-workflow-overview-section">
|
||||
<div className="mythic-eventing-workflow-overview-field">
|
||||
<span className="mythic-eventing-workflow-overview-label">Keywords</span>
|
||||
<div className="mythic-eventing-workflow-chip-row">
|
||||
{keywords.length === 0 ? (
|
||||
<MythicStateChip compact label="No keywords" state="neutral" />
|
||||
) : (
|
||||
<>
|
||||
{visibleKeywords.map((keyword, index) => (
|
||||
<span className="mythic-eventing-workflow-keyword-chip" key={`${keyword}-${index}`}>{keyword}</span>
|
||||
))}
|
||||
{hiddenKeywordCount > 0 &&
|
||||
<MythicStyledTooltip title={keywords.join(", ")}>
|
||||
<span className="mythic-eventing-workflow-keyword-chip mythic-eventing-workflow-keyword-more">+{hiddenKeywordCount} more</span>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mythic-eventing-workflow-overview-field">
|
||||
<span className="mythic-eventing-workflow-overview-label">Run context</span>
|
||||
<div className="mythic-eventing-workflow-chip-row">
|
||||
<MythicStateChip compact label={selectedEventGroup?.run_as || "unknown"} state="neutral" />
|
||||
<Button
|
||||
className={`mythic-eventing-workflow-approval-button mythic-eventing-workflow-approval-${isApproved ? "approved" : "needs-approval"}`.trim()}
|
||||
size="small"
|
||||
startIcon={isApproved ? <ChecklistRtlTwoToneIcon fontSize="small" /> : <RuleTwoToneIcon fontSize="small" />}
|
||||
onClick={onOpenApproval}
|
||||
>
|
||||
{isApproved ? "Approved" : "Needs approval"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mythic-eventing-workflow-overview-section mythic-eventing-workflow-overview-actions">
|
||||
<div className="mythic-eventing-workflow-action-group">
|
||||
<span className="mythic-eventing-workflow-overview-label">Attached details</span>
|
||||
<div className="mythic-eventing-workflow-button-row">
|
||||
<Button
|
||||
className="mythic-table-row-action mythic-table-row-action-hover-info"
|
||||
disabled={!hasTriggerData}
|
||||
size="small"
|
||||
startIcon={<InfoTwoToneIcon fontSize="small" />}
|
||||
onClick={onOpenTriggerData}
|
||||
>
|
||||
Trigger data
|
||||
</Button>
|
||||
<Button
|
||||
className="mythic-table-row-action mythic-table-row-action-hover-info"
|
||||
disabled={!hasEnvironment}
|
||||
size="small"
|
||||
startIcon={<InfoTwoToneIcon fontSize="small" />}
|
||||
onClick={onOpenEnvironment}
|
||||
>
|
||||
Environment
|
||||
</Button>
|
||||
<Button
|
||||
className="mythic-table-row-action mythic-table-row-action-hover-info"
|
||||
size="small"
|
||||
startIcon={
|
||||
<Badge badgeContent={fileCount} color="secondary">
|
||||
<AttachFileIcon fontSize="small" />
|
||||
</Badge>
|
||||
}
|
||||
onClick={onManageFiles}
|
||||
>
|
||||
Files
|
||||
</Button>
|
||||
{consumingContainers.length > 0 &&
|
||||
<Button
|
||||
className={`mythic-table-row-action ${consumingContainersErrors > 0 ? "mythic-table-row-action-hover-danger" : "mythic-table-row-action-hover-info"}`.trim()}
|
||||
size="small"
|
||||
startIcon={
|
||||
<Badge badgeContent={consumingContainersErrors} color="error">
|
||||
<LayersTwoToneIcon fontSize="small" />
|
||||
</Badge>
|
||||
}
|
||||
onClick={onOpenContainers}
|
||||
>
|
||||
Containers
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mythic-eventing-workflow-action-group">
|
||||
<span className="mythic-eventing-workflow-overview-label">Workflow actions</span>
|
||||
<div className="mythic-eventing-workflow-button-row">
|
||||
{selectedEventGroup?.trigger === "manual" &&
|
||||
<Button
|
||||
className="mythic-table-row-action mythic-table-row-action-hover-success"
|
||||
size="small"
|
||||
startIcon={<PlayCircleFilledTwoToneIcon fontSize="small" />}
|
||||
onClick={onTriggerManual}
|
||||
>
|
||||
Run now
|
||||
</Button>
|
||||
}
|
||||
{keywords.length > 0 &&
|
||||
<Button
|
||||
className="mythic-table-row-action mythic-table-row-action-hover-success"
|
||||
size="small"
|
||||
startIcon={<SpellcheckIcon fontSize="small" />}
|
||||
onClick={onOpenKeywordTrigger}
|
||||
>
|
||||
Keyword run
|
||||
</Button>
|
||||
}
|
||||
<MythicStyledTooltip title="Edit workflow metadata and settings, not steps">
|
||||
<Button
|
||||
className="mythic-table-row-action mythic-table-row-action-hover-info"
|
||||
size="small"
|
||||
startIcon={<EditNoteTwoToneIcon fontSize="small" />}
|
||||
onClick={onEdit}
|
||||
>
|
||||
Edit details
|
||||
</Button>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title="Large graph view">
|
||||
<Button
|
||||
className="mythic-table-row-action mythic-table-row-action-hover-info"
|
||||
size="small"
|
||||
startIcon={<OpenInNewTwoToneIcon fontSize="small" />}
|
||||
onClick={onOpenGraph}
|
||||
>
|
||||
Open graph
|
||||
</Button>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title="Create a new workflow using this workflow as the starting point">
|
||||
<Button
|
||||
className="mythic-table-row-action mythic-table-row-action-hover-success"
|
||||
size="small"
|
||||
startIcon={<ContentCopyTwoToneIcon fontSize="small" />}
|
||||
onClick={onClone}
|
||||
>
|
||||
Duplicate workflow
|
||||
</Button>
|
||||
</MythicStyledTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function hasWorkflowDetailValue(value) {
|
||||
if(value === null || value === undefined){
|
||||
return false;
|
||||
}
|
||||
if(typeof value === "string"){
|
||||
return value.trim().length > 0;
|
||||
}
|
||||
if(Array.isArray(value)){
|
||||
return value.length > 0;
|
||||
}
|
||||
if(typeof value === "object"){
|
||||
return Object.keys(value).length > 0;
|
||||
}
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
function RenderSteps({selectedEventGroup, selectedInstanceID}){
|
||||
const getRenderer = () => {
|
||||
if(selectedInstanceID > 0){
|
||||
|
||||
@@ -87,6 +87,7 @@ subscription GetEventStepInstances($eventgroupinstance_id: Int!) {
|
||||
description
|
||||
depends_on
|
||||
order
|
||||
action
|
||||
}
|
||||
created_at
|
||||
updated_at
|
||||
@@ -108,6 +109,7 @@ query getEventStepInstanceDetails($eventstepinstance_id: Int!){
|
||||
created_at
|
||||
order
|
||||
end_timestamp
|
||||
action
|
||||
action_data
|
||||
stdout
|
||||
stderr
|
||||
@@ -554,15 +556,15 @@ function EventNode({data}) {
|
||||
<div className={`mythic-eventing-flow-node mythic-eventing-flow-node-${getEventingStatusClass(data?.status)}`.trim()}>
|
||||
<div className="mythic-eventing-flow-node-main">
|
||||
<EventingStatusChip data={data}/>
|
||||
<Typography className="mythic-eventing-flow-node-title">{data.name}</Typography>
|
||||
{data.status &&
|
||||
<GetTimeDuration data={data} />
|
||||
}
|
||||
</div>
|
||||
<Typography className="mythic-eventing-flow-node-title">{data.name}</Typography>
|
||||
<div className="mythic-eventing-flow-node-meta">
|
||||
{data.action &&
|
||||
<span className="mythic-eventing-flow-node-action">{data.action}</span>
|
||||
}
|
||||
{data.status &&
|
||||
<GetTimeDuration data={data} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<Handle type={"target"} position={targetPosition}/>
|
||||
|
||||
@@ -5157,6 +5157,261 @@ tspan {
|
||||
min-height: 200px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-eventing-workflow-overview {
|
||||
background-color: ${(props) => props.theme.surfaces?.raised || props.theme.palette.background.paper};
|
||||
background-image: ${(props) => `linear-gradient(90deg, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.12 : 0.06)} 0%, ${alpha(props.theme.palette.background.paper, 0)} 48%)`};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
box-shadow: ${(props) => props.theme.palette.mode === "dark" ? "inset 0 1px 0 rgba(255,255,255,0.055)" : "inset 0 1px 0 rgba(255,255,255,0.78)"};
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
display: grid;
|
||||
flex: 0 0 auto;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: minmax(10.5rem, 0.55fr) minmax(16rem, 0.82fr) minmax(31rem, 2.2fr);
|
||||
min-width: 0;
|
||||
overflow: visible;
|
||||
padding: 0.72rem 0.78rem;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-header {
|
||||
align-items: flex-start;
|
||||
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
grid-column: 1 / -1;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
padding-bottom: 0.7rem;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-title-block {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
gap: 0.28rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-title-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-title {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.18;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-description {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.78rem;
|
||||
font-weight: 650;
|
||||
line-height: 1.35;
|
||||
max-width: 64rem;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-header-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
justify-content: flex-end;
|
||||
max-width: 100%;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-header-actions .MuiButton-root.mythic-table-row-action {
|
||||
min-width: 6.5rem;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.62rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-section + .mythic-eventing-workflow-overview-section {
|
||||
border-left: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-primary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.62rem;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-field,
|
||||
.mythic-eventing-workflow-action-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.34rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-label {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.15;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-value {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.86rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-subvalue {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.76rem;
|
||||
font-weight: 650;
|
||||
line-height: 1.3;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-icon-line {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-icon-line svg {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.mythic-eventing-workflow-chip-row,
|
||||
.mythic-eventing-workflow-button-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-workflow-keyword-chip {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.055) : alpha(props.theme.palette.common.black, 0.035)};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: 999px;
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
display: inline-flex;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 760;
|
||||
line-height: 1.1;
|
||||
max-width: 16rem;
|
||||
min-height: 1.35rem;
|
||||
overflow: hidden;
|
||||
padding: 0.18rem 0.46rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-eventing-workflow-keyword-more {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
}
|
||||
.mythic-eventing-workflow-overview-actions {
|
||||
gap: 0.7rem;
|
||||
}
|
||||
.mythic-eventing-workflow-button-row .MuiButton-root.mythic-table-row-action {
|
||||
gap: 0.25rem;
|
||||
min-width: auto;
|
||||
padding-left: 0.55rem;
|
||||
padding-right: 0.65rem;
|
||||
}
|
||||
.mythic-eventing-workflow-button-row .MuiButton-startIcon {
|
||||
margin-left: 0;
|
||||
margin-right: 0.28rem;
|
||||
}
|
||||
.MuiButton-root.mythic-eventing-workflow-approval-button {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.055) : alpha(props.theme.palette.common.black, 0.035)} !important;
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px !important;
|
||||
box-shadow: none !important;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
min-height: 1.5rem;
|
||||
padding: 0.15rem 0.55rem;
|
||||
text-transform: none;
|
||||
}
|
||||
.MuiButton-root.mythic-eventing-workflow-approval-button .MuiButton-startIcon {
|
||||
margin-left: 0;
|
||||
margin-right: 0.28rem;
|
||||
}
|
||||
.MuiButton-root.mythic-eventing-workflow-approval-approved {
|
||||
background-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.48 : 0.32)} !important;
|
||||
color: ${(props) => props.theme.palette.success.main} !important;
|
||||
}
|
||||
.MuiButton-root.mythic-eventing-workflow-approval-approved:hover {
|
||||
background-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.26 : 0.16)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.66 : 0.48)} !important;
|
||||
}
|
||||
.MuiButton-root.mythic-eventing-workflow-approval-needs-approval {
|
||||
background-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.2 : 0.12)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.5 : 0.34)} !important;
|
||||
color: ${(props) => props.theme.palette.warning.main} !important;
|
||||
}
|
||||
.MuiButton-root.mythic-eventing-workflow-approval-needs-approval:hover {
|
||||
background-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.28 : 0.18)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.68 : 0.5)} !important;
|
||||
}
|
||||
@media (max-width: 1180px) {
|
||||
.mythic-eventing-workflow-overview {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-header {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-header-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-section + .mythic-eventing-workflow-overview-section {
|
||||
border-left: 0;
|
||||
border-top: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
padding-left: 0;
|
||||
padding-top: 0.65rem;
|
||||
}
|
||||
}
|
||||
.mythic-eventing-instances-grid {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-eventing-instances-time-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.12rem;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-eventing-instances-time-line {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.38rem;
|
||||
line-height: 1.25;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-eventing-instances-time-line svg {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
.mythic-eventing-instances-time-secondary {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
.mythic-eventing-flow-canvas {
|
||||
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.default};
|
||||
height: 100%;
|
||||
@@ -5254,7 +5509,9 @@ tspan {
|
||||
}
|
||||
.mythic-eventing-flow-node-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user