eventing updates

This commit is contained in:
its-a-feature
2026-05-06 22:31:10 -05:00
parent c913911476
commit f9f1677a1d
5 changed files with 602 additions and 230 deletions
@@ -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}/>
+258 -1
View File
@@ -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;
}