mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
c2 path and eventing wizard updates
This commit is contained in:
@@ -9,7 +9,7 @@ import MenuList from '@mui/material/MenuList';
|
||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Badge from '@mui/material/Badge';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import {alpha, useTheme} from '@mui/material/styles';
|
||||
import {
|
||||
Draggable,
|
||||
} from "@hello-pangea/dnd";
|
||||
@@ -100,6 +100,8 @@ export function MythicTabLabel(props) {
|
||||
{(() => {
|
||||
const selected = selectedIndex === index || snapshot.isDragging;
|
||||
const tabAccent = tabInfo?.color || theme.palette.primary.main;
|
||||
const selectedTabSurface = theme.palette.background.paper;
|
||||
const selectedTabAccent = alpha(tabAccent, theme.palette.mode === "dark" ? 0.34 : 0.2);
|
||||
return (
|
||||
<Tab
|
||||
label={
|
||||
@@ -120,10 +122,14 @@ export function MythicTabLabel(props) {
|
||||
margin: "0 3px 0 0",
|
||||
minHeight: "32px",
|
||||
borderRadius: `${theme.shape.borderRadius}px ${theme.shape.borderRadius}px 0 0`,
|
||||
border: `1px solid ${theme.borderColor}`,
|
||||
borderTop: `2px solid ${selected ? tabAccent : "transparent"}`,
|
||||
borderRight: `1px solid ${theme.borderColor}`,
|
||||
borderBottom: selected ? `1px solid ${theme.palette.background.paper}` : `1px solid ${theme.borderColor}`,
|
||||
backgroundColor: selected ? theme.palette.background.paper : theme.surfaces?.muted,
|
||||
borderLeft: `1px solid ${theme.borderColor}`,
|
||||
backgroundColor: selected ? selectedTabSurface : theme.surfaces?.muted,
|
||||
backgroundImage: selected ?
|
||||
`linear-gradient(180deg, ${selectedTabAccent} 0%, ${selectedTabSurface} 82%)` :
|
||||
"none",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {useEffect, useCallback, useMemo, useState} from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import ELK from 'elkjs/lib/elk.bundled.js';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
@@ -39,6 +39,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
||||
import { MythicAgentSVGIconNoTooltip} from "../../MythicComponents/MythicAgentSVGIcon";
|
||||
import {ImageWithAuth} from "../../utilities/ImageWithAuth";
|
||||
import {MythicDialogButton, MythicDialogFooter} from "../../MythicComponents/MythicDialogLayout";
|
||||
|
||||
const ITEM_HEIGHT = 48;
|
||||
const ITEM_PADDING_TOP = 8;
|
||||
@@ -249,23 +250,50 @@ export function C2PathDialog({callback, callbackgraphedges, onClose, onOpenTab})
|
||||
},
|
||||
]}, [getLinkCommands, hideCallback, viewConfig, onOpenTab]);
|
||||
|
||||
const activeEdgeCount = callbackgraphedges?.filter(edge => edge.end_timestamp === null).length || 0;
|
||||
const totalEdgeCount = callbackgraphedges?.length || 0;
|
||||
const egressEdgeCount = callbackgraphedges?.filter(edge => !edge.c2profile?.is_p2p).length || 0;
|
||||
const p2pEdgeCount = callbackgraphedges?.filter(edge => edge.c2profile?.is_p2p).length || 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{padding: "10px"}}>
|
||||
<Typography variant='h4' style={{display:"inline-block", marginTop: "10px"}}>
|
||||
Callback {callback.display_id}'s Egress Path
|
||||
</Typography>
|
||||
<div style={{float: "right"}}>
|
||||
<FormControl sx={{ width: 200, marginTop: "10px" }}>
|
||||
<InputLabel id="demo-chip-label">Group Callbacks By</InputLabel>
|
||||
<DialogTitle className="mythic-c2-path-title">
|
||||
<div className="mythic-c2-path-title-row">
|
||||
<div className="mythic-c2-path-title-copy">
|
||||
<Typography component="div" className="mythic-c2-path-title-text">
|
||||
Callback {callback.display_id}'s Egress Path
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-c2-path-title-subtitle">
|
||||
Review routes, grouping, and link-tasking options for this callback.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="mythic-c2-path-summary">
|
||||
<span className="mythic-c2-path-summary-chip">{activeEdgeCount} active</span>
|
||||
<span className="mythic-c2-path-summary-chip">{totalEdgeCount} total</span>
|
||||
<span className="mythic-c2-path-summary-chip">{egressEdgeCount} egress</span>
|
||||
<span className="mythic-c2-path-summary-chip">{p2pEdgeCount} p2p</span>
|
||||
</div>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent dividers={true} className="mythic-c2-path-content">
|
||||
<div className="mythic-c2-path-toolbar">
|
||||
<div className="mythic-c2-path-toolbar-copy">
|
||||
<Typography component="div" className="mythic-c2-path-toolbar-title">
|
||||
Graph View
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-c2-path-toolbar-description">
|
||||
Adjust labels and grouping without changing callback state.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="mythic-c2-path-controls">
|
||||
<FormControl size="small" className="mythic-c2-path-control">
|
||||
<InputLabel id="c2-path-group-label">Group By</InputLabel>
|
||||
<Select
|
||||
labelId="demo-chip-label"
|
||||
id="demo-chip"
|
||||
|
||||
labelId="c2-path-group-label"
|
||||
id="c2-path-group"
|
||||
value={selectedGroupBy}
|
||||
onChange={handleGroupByChange}
|
||||
input={<OutlinedInput id="select-chip" label="Group Callbacks By" />}
|
||||
input={<OutlinedInput id="select-c2-path-group" label="Group By" />}
|
||||
>
|
||||
{groupByOptions.map((name) => (
|
||||
<MenuItem
|
||||
@@ -277,16 +305,17 @@ export function C2PathDialog({callback, callbackgraphedges, onClose, onOpenTab})
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl sx={{minWidth: 300, marginTop: "10px"}}>
|
||||
<InputLabel id="demo-multiple-chip-label">Display Properties per Callback</InputLabel>
|
||||
<FormControl size="small" className="mythic-c2-path-control mythic-c2-path-control-wide">
|
||||
<InputLabel id="c2-path-label-components-label">Display Properties</InputLabel>
|
||||
<Select
|
||||
labelId="demo-multiple-chip-label"
|
||||
id="demo-multiple-chip"
|
||||
labelId="c2-path-label-components-label"
|
||||
id="c2-path-label-components"
|
||||
multiple
|
||||
value={selectedComponentOptions}
|
||||
onChange={handleChange}
|
||||
input={<OutlinedInput id="select-multiple-chip" label="Display Properties per Callback" />}
|
||||
input={<OutlinedInput id="select-c2-path-label-components" label="Display Properties" />}
|
||||
MenuProps={MenuProps}
|
||||
renderValue={(selected) => selected.join(", ")}
|
||||
>
|
||||
{labelComponentOptions.map((name) => (
|
||||
<MenuItem
|
||||
@@ -299,9 +328,8 @@ export function C2PathDialog({callback, callbackgraphedges, onClose, onOpenTab})
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogContent style={{height: "calc(70vh)" }}>
|
||||
{manuallyRemoveEdgeDialogOpen &&
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={manuallyRemoveEdgeDialogOpen}
|
||||
onClose={()=>{setManuallyRemoveEdgeDialogOpen(false);}}
|
||||
@@ -330,19 +358,21 @@ export function C2PathDialog({callback, callbackgraphedges, onClose, onOpenTab})
|
||||
action={"select"} display={"display"} identifier={"display"}/>}
|
||||
/>
|
||||
}
|
||||
<DrawC2PathElementsFlowWithProvider
|
||||
providedNodes={[callback]}
|
||||
edges={callbackgraphedges}
|
||||
view_config={viewConfig}
|
||||
theme={theme}
|
||||
contextMenu={contextMenu}
|
||||
/>
|
||||
<div className="mythic-c2-path-canvas">
|
||||
<DrawC2PathElementsFlowWithProvider
|
||||
providedNodes={[callback]}
|
||||
edges={callbackgraphedges}
|
||||
view_config={viewConfig}
|
||||
theme={theme}
|
||||
contextMenu={contextMenu}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="contained" color="primary">
|
||||
<MythicDialogFooter>
|
||||
<MythicDialogButton onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</MythicDialogButton>
|
||||
</MythicDialogFooter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -226,8 +226,10 @@ export function Callbacks({me}) {
|
||||
alignItems: "center",
|
||||
overflow: "hidden",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.borderColor}`,
|
||||
borderTop: `1px solid ${theme.borderColor}`,
|
||||
borderRight: 0,
|
||||
borderBottom: `1px solid ${theme.borderColor}`,
|
||||
borderLeft: `1px solid ${theme.borderColor}`,
|
||||
borderRadius: `${theme.shape.borderRadius}px 0 0 ${theme.shape.borderRadius}px`,
|
||||
paddingTop: "3px",
|
||||
}}>
|
||||
|
||||
@@ -23,7 +23,6 @@ import {CallbacksTabsHideMultipleDialog, CallbacksTabsSelectMultipleDialog} from
|
||||
import {CallbacksTabsTaskMultipleDialog} from "./CallbacksTabsTaskMultipleDialog";
|
||||
import ip6 from 'ip6';
|
||||
import {CallbacksContext, OnOpenTabContext, OnOpenTabsContext} from "./CallbacksTop";
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import {GetMythicSetting, useSetMythicSetting, GetComputedFontSize} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {DetailedCallbackTable} from "./DetailedCallbackTable";
|
||||
import {ModifyCallbackMythicTreeGroupsDialog} from "./ModifyCallbackMythicTreeGroupsDialog";
|
||||
@@ -53,10 +52,10 @@ import NotificationsOffTwoToneIcon from '@mui/icons-material/NotificationsOffTwo
|
||||
import {CallbacksTableEditTriggerOnCheckinDialog} from "./CallbacksTableEditTriggerOnCheckinDialog";
|
||||
import {CallbacksTableColumnsReorderDialog} from "./CallbacksTableColumnsReorderDialog";
|
||||
import PlayCircleFilledTwoToneIcon from '@mui/icons-material/PlayCircleFilledTwoTone';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import {EventTriggerContextSelectDialog} from "../Eventing/EventTriggerContextSelect";
|
||||
import {useMythicLazyQuery} from "../../utilities/useMythicLazyQuery";
|
||||
import {faFilter} from '@fortawesome/free-solid-svg-icons';
|
||||
import {getReadableTextColor, isValidHexColor} from "../../MythicComponents/MythicColorInput";
|
||||
|
||||
export const getCustomBrowsersQuery = gql(`
|
||||
query getCustomBrowsersQuery{
|
||||
@@ -132,12 +131,20 @@ const callbackTableInitialColumns = [
|
||||
{key: "process_short_name", type: 'string', name: "Process Name", fillWidth: true},
|
||||
{key: "tags", type: 'tags', name: "Tags", fillWidth: false, disableDoubleClick: true, width: 150}
|
||||
];
|
||||
const getCallbackRowStyle = (row) => {
|
||||
if(!isValidHexColor(row.color)){
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
backgroundColor: row.color,
|
||||
color: getReadableTextColor(row.color),
|
||||
};
|
||||
}
|
||||
function CallbacksTablePreMemo(props){
|
||||
const callbacks = useContext(CallbacksContext);
|
||||
const onOpenTab = useContext(OnOpenTabContext);
|
||||
const onOpenTabs = useContext(OnOpenTabsContext);
|
||||
const interactType = GetMythicSetting({setting_name: "interactType", default_value: "interact", output: "string"});
|
||||
const theme = useTheme();
|
||||
const [loadingSettings, setLoadingSettings] = React.useState(true);
|
||||
const [columnOrder, setColumnOrder] = React.useState(callbackTableInitialColumns);
|
||||
const [openReorderDialog, setOpenReorderDialog] = React.useState(false);
|
||||
@@ -155,6 +162,7 @@ function CallbacksTablePreMemo(props){
|
||||
setOpenEditMythicTreeGroupsDialog(true);
|
||||
}
|
||||
const [openEditDescriptionDialog, setOpenEditDescriptionDialog] = React.useState(false);
|
||||
const [callbackLocalUpdates, setCallbackLocalUpdates] = React.useState({});
|
||||
const [updateDescriptionMutation] = useMutation(updateDescriptionCallbackMutation, {
|
||||
update: (cache, {data}) => {
|
||||
if(data.updateCallback.status === "success"){
|
||||
@@ -170,6 +178,16 @@ function CallbacksTablePreMemo(props){
|
||||
}
|
||||
});
|
||||
const updateDescriptionRef = React.useRef({payload_description: "", callback_display_id: 0, description: "", color: ""});
|
||||
const applyCallbackLocalUpdate = React.useCallback(({callback_display_id, description, color}) => {
|
||||
setCallbackLocalUpdates((currentUpdates) => ({
|
||||
...currentUpdates,
|
||||
[String(callback_display_id)]: {description, color},
|
||||
}));
|
||||
const callbackIndex = callbacks.findIndex((callback) => callback.display_id === callback_display_id);
|
||||
if(callbackIndex !== -1){
|
||||
callbacks[callbackIndex] = {...callbacks[callbackIndex], description, color};
|
||||
}
|
||||
}, [callbacks]);
|
||||
const updateDescription = ({payload_description, callback_display_id, description, color}) => {
|
||||
updateDescriptionRef.current = {
|
||||
payload_description: payload_description,
|
||||
@@ -196,8 +214,26 @@ function CallbacksTablePreMemo(props){
|
||||
}
|
||||
}
|
||||
const updateDescriptionSubmit = React.useCallback( ({callback_display_id, description, color}) => {
|
||||
updateDescriptionMutation({variables: {callback_display_id: callback_display_id, description, color}})
|
||||
}, []);
|
||||
const previousCallback = callbacks.find((callback) => callback.display_id === callback_display_id);
|
||||
applyCallbackLocalUpdate({callback_display_id, description, color});
|
||||
updateDescriptionMutation({variables: {callback_display_id: callback_display_id, description, color}}).then(({data}) => {
|
||||
if(data?.updateCallback?.status !== "success" && previousCallback){
|
||||
applyCallbackLocalUpdate({
|
||||
callback_display_id,
|
||||
description: previousCallback.description,
|
||||
color: previousCallback.color,
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
if(previousCallback){
|
||||
applyCallbackLocalUpdate({
|
||||
callback_display_id,
|
||||
description: previousCallback.description,
|
||||
color: previousCallback.color,
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [applyCallbackLocalUpdate, callbacks, updateDescriptionMutation]);
|
||||
const [openCallbackDropdown, setOpenCallbackDropdown] = React.useState(false);
|
||||
const callbackDropdownRef = React.useRef({options: [], callback: {}});
|
||||
const [clickedCallbackID, setClickedCallbackId] = React.useState(0);
|
||||
@@ -349,19 +385,41 @@ function CallbacksTablePreMemo(props){
|
||||
async function getCustomBrowsersForContextMenu(){
|
||||
return await getCustomBrowsers({}).then(result => {return result.data?.custombrowser});
|
||||
}
|
||||
const callbackMenuIcon = (icon, intent = "neutral") => (
|
||||
<span className={`mythic-callback-action-menu-icon mythic-callback-action-menu-icon-${intent}`}>
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
const callbackMenuLabel = (primary, secondary) => (
|
||||
<span className="mythic-callback-action-menu-label">
|
||||
<span className="mythic-callback-action-menu-label-primary">{primary}</span>
|
||||
{secondary &&
|
||||
<span className="mythic-callback-action-menu-label-secondary">{secondary}</span>
|
||||
}
|
||||
</span>
|
||||
);
|
||||
const callbackMenuSection = (label) => ({
|
||||
name: <span className="mythic-callback-action-menu-section-label">{label}</span>,
|
||||
icon: null,
|
||||
click: ({event}) => {},
|
||||
type: "item",
|
||||
disabled: true,
|
||||
className: "mythic-callback-action-menu-section"
|
||||
});
|
||||
async function onRowContextClick({rowDataStatic}) {
|
||||
// based on row, return updated options array?
|
||||
let defaultInteractIcon = <KeyboardIcon style={{paddingRight: "5px"}}/>;
|
||||
let defaultInteractIcon = <KeyboardIcon fontSize="small"/>;
|
||||
if(interactType === "interactSplit"){
|
||||
defaultInteractIcon = <VerticalSplitIcon style={{paddingRight: "5px"}}/>;
|
||||
defaultInteractIcon = <VerticalSplitIcon fontSize="small"/>;
|
||||
} else if(interactType === "interactConsole"){
|
||||
defaultInteractIcon = <TerminalIcon style={{paddingRight: "5px"}}/>;
|
||||
defaultInteractIcon = <TerminalIcon fontSize="small"/>;
|
||||
}
|
||||
let customBrowsers = await getCustomBrowsersForContextMenu();
|
||||
customBrowsers = customBrowsers.map(b => {
|
||||
return {
|
||||
name: b.name,
|
||||
icon: b.type === 'file' ? <FontAwesomeIcon icon={faFolderOpen} style={{color: theme.folderColor, cursor: "pointer", marginRight: "10px"}} /> : <AccountTreeIcon style={{paddingRight: "5px"}}/>,
|
||||
icon: callbackMenuIcon(b.type === 'file' ? <FontAwesomeIcon icon={faFolderOpen} /> : <AccountTreeIcon fontSize="small" />),
|
||||
className: "mythic-callback-action-menu-item",
|
||||
click: ({event}) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -379,20 +437,15 @@ function CallbacksTablePreMemo(props){
|
||||
})
|
||||
return [
|
||||
{
|
||||
name: <div style={{display: "flex", flexDirection: "column"}}>
|
||||
<Typography>
|
||||
{"Callback: " + rowDataStatic.display_id}
|
||||
</Typography>
|
||||
<Typography style={{fontSize: theme?.typography.pxToRem(12),}}>
|
||||
{rowDataStatic.user + "@" + rowDataStatic.host}
|
||||
</Typography>
|
||||
</div>,
|
||||
name: callbackMenuLabel("Callback " + rowDataStatic.display_id, rowDataStatic.user + "@" + rowDataStatic.host),
|
||||
icon: null, click: ({event}) => {},
|
||||
type: "item",
|
||||
disabled: true
|
||||
disabled: true,
|
||||
className: "mythic-callback-action-menu-header"
|
||||
},
|
||||
callbackMenuSection("Tasking"),
|
||||
{
|
||||
name: "Interact", icon: defaultInteractIcon, click: ({event}) => {
|
||||
name: "Interact", icon: callbackMenuIcon(defaultInteractIcon, "primary"), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
const tabType = interactType;
|
||||
onOpenTab({
|
||||
@@ -404,7 +457,55 @@ function CallbacksTablePreMemo(props){
|
||||
}, type: "item"
|
||||
},
|
||||
{
|
||||
name: "Edit Description and Color", icon: <EditIcon style={{paddingRight: "5px"}} />, click:({event}) => {
|
||||
name: "Tasking Views", icon: callbackMenuIcon(<VerticalSplitIcon fontSize="small" />), click: () => {}, type: "menu", className: "mythic-callback-action-menu-item",
|
||||
menuItems: [
|
||||
{
|
||||
name: 'Default Tasking', icon: callbackMenuIcon(<KeyboardIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
const tabType = "interact";
|
||||
onOpenTab({
|
||||
tabType: tabType,
|
||||
tabID: rowDataStatic.id + tabType,
|
||||
callbackID: rowDataStatic.id,
|
||||
color: rowDataStatic.color,
|
||||
displayID: rowDataStatic.display_id});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Split Tasking', icon: callbackMenuIcon(<VerticalSplitIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
const tabType = "interactSplit";
|
||||
onOpenTab({
|
||||
tabType: tabType,
|
||||
tabID: rowDataStatic.id + tabType,
|
||||
callbackID: rowDataStatic.id,
|
||||
color: rowDataStatic.color,
|
||||
displayID: rowDataStatic.display_id});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Console View", icon: callbackMenuIcon(<TerminalIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
const tabType = "interactConsole";
|
||||
onOpenTab({
|
||||
tabType: tabType,
|
||||
tabID: rowDataStatic.id + tabType,
|
||||
callbackID: rowDataStatic.id,
|
||||
color: rowDataStatic.color,
|
||||
displayID: rowDataStatic.display_id});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Expand Callback", icon: callbackMenuIcon(<OpenInNewIcon fontSize="small" />), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
window.open("/new/callbacks/" + rowDataStatic.display_id, "_blank").focus();
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
callbackMenuSection("Callback State"),
|
||||
{
|
||||
name: "Edit Description and Color", icon: callbackMenuIcon(<EditIcon fontSize="small" />), className: "mythic-callback-action-menu-item", click:({event}) => {
|
||||
event.stopPropagation();
|
||||
updateDescription({payload_description: rowDataStatic.payload.description,
|
||||
callback_display_id: rowDataStatic.display_id,
|
||||
@@ -413,29 +514,10 @@ function CallbacksTablePreMemo(props){
|
||||
});
|
||||
}, type: "item"
|
||||
},
|
||||
{
|
||||
name: 'Hide Callback', icon: <VisibilityOffIcon color={"warning"} style={{paddingRight: "5px"}}/>, click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
hideCallback({variables: {callback_display_id: rowDataStatic.display_id}});
|
||||
}, type: "item"
|
||||
},
|
||||
{
|
||||
name: "Exit Callback", icon: <FontAwesomeIcon icon={faSkullCrossbones} style={{color: theme.palette.error.main, cursor: "pointer", marginRight: "8px"}} />,
|
||||
click: ({event}) => {
|
||||
taskingData.current = {
|
||||
"parameters": "",
|
||||
"ui_feature": "callback_table:exit",
|
||||
"getConfirmation": true,
|
||||
id: rowDataStatic.id,
|
||||
display_id: rowDataStatic.display_id,
|
||||
acceptText: "exit"
|
||||
};
|
||||
setOpenTaskingButton(true);
|
||||
}, type: "item"
|
||||
},
|
||||
{
|
||||
name: rowDataStatic.locked ? 'Unlock (Locked by ' + rowDataStatic.locked_operator.username + ')' : 'Lock Callback',
|
||||
icon: rowDataStatic.locked ? (<LockIcon color={"error"} style={{paddingRight: "5px"}}/>) : (<LockOpenIcon color={"success"} style={{paddingRight: "5px"}} />),
|
||||
icon: callbackMenuIcon(rowDataStatic.locked ? <LockIcon fontSize="small"/> : <LockOpenIcon fontSize="small" />, rowDataStatic.locked ? "warning" : "neutral"),
|
||||
className: "mythic-callback-action-menu-item",
|
||||
click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
if(rowDataStatic.locked){
|
||||
@@ -448,17 +530,39 @@ function CallbacksTablePreMemo(props){
|
||||
{
|
||||
name: rowDataStatic.trigger_on_checkin_after_time > 0 ? "Adjust Alert Trigger" : "Add New Alert Trigger",
|
||||
type: "item",
|
||||
icon: rowDataStatic.trigger_on_checkin_after_time > 0 ? (<NotificationsOffTwoToneIcon color={"warning"} style={{paddingRight: "5px"}} />) : (<NotificationsActiveTwoToneIcon color={"success"} style={{paddingRight: "5px"}}/>),
|
||||
icon: callbackMenuIcon(rowDataStatic.trigger_on_checkin_after_time > 0 ? <NotificationsOffTwoToneIcon fontSize="small" /> : <NotificationsActiveTwoToneIcon fontSize="small"/>, rowDataStatic.trigger_on_checkin_after_time > 0 ? "warning" : "neutral"),
|
||||
className: "mythic-callback-action-menu-item",
|
||||
click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
setOpenTriggerDialog({open: true, trigger_on_checkin_after_time: rowDataStatic.trigger_on_checkin_after_time, display_id: rowDataStatic.display_id})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Eventing", icon: <PlayCircleFilledTwoToneIcon />, click: (event) => {}, type: "menu",
|
||||
name: 'Hide Callback', icon: callbackMenuIcon(<VisibilityOffIcon fontSize="small"/>, "warning"), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
hideCallback({variables: {callback_display_id: rowDataStatic.display_id}});
|
||||
}, type: "item"
|
||||
},
|
||||
{
|
||||
name: "Exit Callback", icon: callbackMenuIcon(<FontAwesomeIcon icon={faSkullCrossbones} />, "error"), className: "mythic-callback-action-menu-item",
|
||||
click: ({event}) => {
|
||||
taskingData.current = {
|
||||
"parameters": "",
|
||||
"ui_feature": "callback_table:exit",
|
||||
"getConfirmation": true,
|
||||
id: rowDataStatic.id,
|
||||
display_id: rowDataStatic.display_id,
|
||||
acceptText: "exit"
|
||||
};
|
||||
setOpenTaskingButton(true);
|
||||
}, type: "item"
|
||||
},
|
||||
callbackMenuSection("Workflows and Browsers"),
|
||||
{
|
||||
name: "Eventing", icon: callbackMenuIcon(<PlayCircleFilledTwoToneIcon fontSize="small" />), click: (event) => {}, type: "menu", className: "mythic-callback-action-menu-item",
|
||||
menuItems: [
|
||||
{
|
||||
name: "Start Eventing Workflow", icon: <PlayCircleFilledTwoToneIcon style={{paddingRight: "5px"}} />,
|
||||
name: "Start Eventing Workflow", icon: callbackMenuIcon(<PlayCircleFilledTwoToneIcon fontSize="small" />), className: "mythic-callback-action-menu-item",
|
||||
click: ({event}) => {
|
||||
eventingDataRef.current = {
|
||||
name: "callback_id",
|
||||
@@ -470,10 +574,10 @@ function CallbacksTablePreMemo(props){
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Browsers", icon: null, click: () => {}, type: "menu",
|
||||
name: "Browsers", icon: callbackMenuIcon(<AccountTreeIcon fontSize="small" />), click: () => {}, type: "menu", className: "mythic-callback-action-menu-item",
|
||||
menuItems: [
|
||||
{
|
||||
name: 'File Browser', icon: <FontAwesomeIcon icon={faFolderOpen} style={{color: theme.folderColor, cursor: "pointer", marginRight: "10px"}} />,
|
||||
name: 'File Browser', icon: callbackMenuIcon(<FontAwesomeIcon icon={faFolderOpen} />), className: "mythic-callback-action-menu-item",
|
||||
click: ({event}) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -487,7 +591,7 @@ function CallbacksTablePreMemo(props){
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Process Browser', icon: <AccountTreeIcon style={{paddingRight: "5px"}}/>,
|
||||
name: 'Process Browser', icon: callbackMenuIcon(<AccountTreeIcon fontSize="small"/>), className: "mythic-callback-action-menu-item",
|
||||
click: ({event}) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -501,105 +605,63 @@ function CallbacksTablePreMemo(props){
|
||||
}
|
||||
},
|
||||
{
|
||||
name: <div style={{display: "flex", flexDirection: "column", width: "100%"}}>
|
||||
<Typography>
|
||||
{"Custom Agent Browsers:"}
|
||||
</Typography>
|
||||
</div>,
|
||||
name: <span className="mythic-callback-action-menu-section-label">Custom Agent Browsers</span>,
|
||||
icon: null, click: ({event}) => {},
|
||||
type: "item",
|
||||
disabled: true
|
||||
disabled: true,
|
||||
className: "mythic-callback-action-menu-section"
|
||||
},
|
||||
...customBrowsers
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Bulk Actions", icon: null, click: (event) => { }, type: "menu",
|
||||
name: "Other Callbacks", icon: callbackMenuIcon(<OpenInNewIcon fontSize="small" />), click: () => {}, type: "menu", className: "mythic-callback-action-menu-item",
|
||||
menuItems: [
|
||||
{
|
||||
name: "Hide Multiple", icon: <VisibilityOffIcon color={"warning"} style={{paddingRight: "5px"}}/>,
|
||||
click: ({event}) => {
|
||||
setOpenHideMultipleDialog(true);
|
||||
name: "Interact", icon: callbackMenuIcon(<KeyboardIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
setOpenMultipleTabsDialog({open: true, tabType: "interact"});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Task Multiple", icon: <FontAwesomeIcon icon={faList} style={{cursor: "pointer", marginRight: "10px"}} />,
|
||||
click: ({event}) => {
|
||||
setOpenTaskMultipleDialog({open: true, data: rowDataStatic});
|
||||
name: "Split Tasking", icon: callbackMenuIcon(<VerticalSplitIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
setOpenMultipleTabsDialog({open: true, tabType: "interactSplit"});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Start Multiple Eventing Workflows", icon: <PlayCircleFilledTwoToneIcon style={{paddingRight: "5px"}}/>,
|
||||
click: ({event}) => {
|
||||
setOpenEventingMultipleDialog(true);
|
||||
name: "Console View", icon: callbackMenuIcon(<TerminalIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
setOpenMultipleTabsDialog({open: true, tabType: "interactConsole"});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "File Browser", icon: callbackMenuIcon(<FontAwesomeIcon icon={faFolderOpen} />), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
setOpenMultipleTabsDialog({open: true, tabType: "fileBrowser"});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Process Browser", icon: callbackMenuIcon(<AccountTreeIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
setOpenMultipleTabsDialog({open: true, tabType: "processBrowser"});
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
callbackMenuSection("Metadata"),
|
||||
{
|
||||
name: "Tasking Views", icon: null, click: () => {}, type: "menu",
|
||||
name: "Metadata", icon: callbackMenuIcon(<InfoIcon fontSize="small" />), click: () => {}, type: "menu", className: "mythic-callback-action-menu-item",
|
||||
menuItems: [
|
||||
{
|
||||
name: 'Default Tasking', icon: <KeyboardIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
const tabType = "interact";
|
||||
onOpenTab({
|
||||
tabType: tabType,
|
||||
tabID: rowDataStatic.id + tabType,
|
||||
callbackID: rowDataStatic.id,
|
||||
color: rowDataStatic.color,
|
||||
displayID: rowDataStatic.display_id});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Split Tasking', icon: <VerticalSplitIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
const tabType = "interactSplit";
|
||||
onOpenTab({
|
||||
tabType: tabType,
|
||||
tabID: rowDataStatic.id + tabType,
|
||||
callbackID: rowDataStatic.id,
|
||||
color: rowDataStatic.color,
|
||||
displayID: rowDataStatic.display_id});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Console View", icon: <TerminalIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
const tabType = "interactConsole";
|
||||
onOpenTab({
|
||||
tabType: tabType,
|
||||
tabID: rowDataStatic.id + tabType,
|
||||
callbackID: rowDataStatic.id,
|
||||
color: rowDataStatic.color,
|
||||
displayID: rowDataStatic.display_id});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Expand Callback", icon: <OpenInNewIcon style={{paddingRight: "5px"}} />, click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
window.open("/new/callbacks/" + rowDataStatic.display_id, "_blank").focus();
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Metadata", icon: null, click: () => {}, type: "menu",
|
||||
menuItems: [
|
||||
{
|
||||
name: "Export Callback", icon: <ImportExportIcon style={{paddingRight: "5px"}} />, click: ({event}) => {
|
||||
name: "Export Callback", icon: callbackMenuIcon(<ImportExportIcon fontSize="small" />), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
exportConfig({variables: {agent_callback_id: rowDataStatic.agent_callback_id}});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "View Metadata", icon: <InfoIcon color={"info"} style={{paddingRight: "5px"}} />, click: ({event}) => {
|
||||
name: "View Metadata", icon: callbackMenuIcon(<InfoIcon fontSize="small" />), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
metaDialog(rowDataStatic.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Modify Groupings", icon: <WidgetsIcon color={"info"} style={{paddingRight: "5px"}} />, click: ({event}) => {
|
||||
name: "Modify Groupings", icon: callbackMenuIcon(<WidgetsIcon fontSize="small" />), className: "mythic-callback-action-menu-item", click: ({event}) => {
|
||||
event.stopPropagation();
|
||||
editMythicTreeGroupsDialog(rowDataStatic.id);
|
||||
}
|
||||
@@ -607,31 +669,24 @@ function CallbacksTablePreMemo(props){
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Other Callbacks", icon: null, click: () => {}, type: "menu",
|
||||
name: "Bulk Actions", icon: callbackMenuIcon(<FontAwesomeIcon icon={faList} />), click: (event) => { }, type: "menu", className: "mythic-callback-action-menu-item",
|
||||
menuItems: [
|
||||
{
|
||||
name: "Interact", icon: <KeyboardIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
|
||||
setOpenMultipleTabsDialog({open: true, tabType: "interact"});
|
||||
name: "Hide Multiple", icon: callbackMenuIcon(<VisibilityOffIcon fontSize="small"/>, "warning"), className: "mythic-callback-action-menu-item",
|
||||
click: ({event}) => {
|
||||
setOpenHideMultipleDialog(true);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Split Tasking", icon: <VerticalSplitIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
|
||||
setOpenMultipleTabsDialog({open: true, tabType: "interactSplit"});
|
||||
name: "Task Multiple", icon: callbackMenuIcon(<FontAwesomeIcon icon={faList} />), className: "mythic-callback-action-menu-item",
|
||||
click: ({event}) => {
|
||||
setOpenTaskMultipleDialog({open: true, data: rowDataStatic});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Console View", icon: <TerminalIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
|
||||
setOpenMultipleTabsDialog({open: true, tabType: "interactConsole"});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "File Browser", icon: <FontAwesomeIcon icon={faFolderOpen} style={{color: theme.folderColor, cursor: "pointer", marginRight: "10px"}} />, click: ({event}) => {
|
||||
setOpenMultipleTabsDialog({open: true, tabType: "fileBrowser"});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Process Browser", icon: <AccountTreeIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
|
||||
setOpenMultipleTabsDialog({open: true, tabType: "processBrowser"});
|
||||
name: "Start Multiple Eventing Workflows", icon: callbackMenuIcon(<PlayCircleFilledTwoToneIcon fontSize="small"/>), className: "mythic-callback-action-menu-item",
|
||||
click: ({event}) => {
|
||||
setOpenEventingMultipleDialog(true);
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -779,7 +834,10 @@ function CallbacksTablePreMemo(props){
|
||||
return false;
|
||||
}
|
||||
const sortedData = React.useMemo(() => {
|
||||
const tempData = [...callbacks];
|
||||
const tempData = callbacks.map((callback) => {
|
||||
const localUpdate = callbackLocalUpdates[String(callback.display_id)];
|
||||
return localUpdate ? {...callback, ...localUpdate} : callback;
|
||||
});
|
||||
|
||||
if (sortData.sortType === 'number' || sortData.sortType === 'size' || sortData.sortType === 'date') {
|
||||
tempData.sort((a, b) => (parseInt(a[sortData.sortKey]) > parseInt(b[sortData.sortKey]) ? 1 : -1));
|
||||
@@ -823,7 +881,7 @@ function CallbacksTablePreMemo(props){
|
||||
return <CallbacksTableIDCell
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}}
|
||||
key={`callback${row.id}_${c.name}`}
|
||||
callbackDropdown={callbackDropdown}
|
||||
@@ -834,7 +892,7 @@ function CallbacksTablePreMemo(props){
|
||||
cellData={row.mythictree_groups.join(", ")}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}}
|
||||
/>;
|
||||
case "IP":
|
||||
@@ -843,105 +901,105 @@ function CallbacksTablePreMemo(props){
|
||||
cellData={row.ip}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}}
|
||||
callback_id={row.id} />;
|
||||
case "External IP":
|
||||
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.external_ip}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}} />;
|
||||
case "Host":
|
||||
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.host}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}} />;
|
||||
case "User":
|
||||
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.user + (row.impersonation_context === "" ? "" : ` [${row.impersonation_context}]`)}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}} />;
|
||||
case "Domain":
|
||||
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.domain}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}} />;
|
||||
case "OS":
|
||||
return <CallbacksTableOSCell key={`callback${row.id}_${c.name}`}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}}
|
||||
cellData={row.os} />;
|
||||
case "Arch":
|
||||
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}}
|
||||
cellData={row.architecture} />;
|
||||
case "PID":
|
||||
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.pid}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}} />;
|
||||
case "Last Checkin":
|
||||
return <CallbacksTableLastCheckinCell key={`callback${row.id}_${c.name}`} me={props.me}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}}
|
||||
cellData={row.last_checkin} />;
|
||||
case "Description":
|
||||
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.description}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}} />;
|
||||
case "Sleep":
|
||||
return <CallbacksTableSleepCell key={`callback${row.id}_${c.name}`}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}}
|
||||
cellData={row.sleep_info} updateSleepInfo={updateSleepInfo} />;
|
||||
case "Agent":
|
||||
return <CallbacksTablePayloadTypeCell key={`callback${row.id}_${c.name}`}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}}
|
||||
cellData={row.payload.payloadtype.name}/>;
|
||||
case "C2":
|
||||
return <CallbacksTableC2Cell key={`callback${row.id}_c2`}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}} />
|
||||
case "Process Name":
|
||||
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`}
|
||||
cellData={row.process_short_name}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}} />;
|
||||
case "Tags":
|
||||
return <CallbacksTableTagsCell key={`callback${row.id}_${c.name}`}
|
||||
cellData={row.tags}
|
||||
rowData={{...row,
|
||||
selected: row.id === clickedCallbackID,
|
||||
rowStyle: {backgroundColor: `${row.color}`},
|
||||
rowStyle: getCallbackRowStyle(row),
|
||||
}} />;
|
||||
}
|
||||
})];
|
||||
}
|
||||
}, [])
|
||||
}, [callbacks, sortData, filterOptions, columnVisibility, clickedCallbackID]);
|
||||
}, [callbacks, callbackLocalUpdates, sortData, filterOptions, columnVisibility, clickedCallbackID]);
|
||||
const onSubmitFilterOptions = (value) => {
|
||||
setFilterOptions({...filterOptions, [selectedColumn.key]: value });
|
||||
try{
|
||||
@@ -1087,11 +1145,13 @@ function CallbacksTablePreMemo(props){
|
||||
isOpen={callbackDropdownRef.current.dropdownAnchorRef}
|
||||
onOpen={setOpenCallbackDropdown}
|
||||
externallyOpen={openCallbackDropdown}
|
||||
minWidth={285}
|
||||
menu={
|
||||
callbackDropdownRef.current.options.map((option, index) => (
|
||||
option.type === 'item' ? (
|
||||
<DropdownMenuItem
|
||||
key={option.name}
|
||||
key={"callback-action-" + index}
|
||||
className={option.className}
|
||||
disabled={option.disabled}
|
||||
onClick={(event) => handleMenuItemClick(event, option.click)}
|
||||
>
|
||||
@@ -1099,12 +1159,15 @@ function CallbacksTablePreMemo(props){
|
||||
</DropdownMenuItem>
|
||||
) : option.type === 'menu' ? (
|
||||
<DropdownNestedMenuItem
|
||||
label={option.name}
|
||||
key={"callback-action-" + index}
|
||||
className={option.className}
|
||||
label={<span className="mythic-callback-action-menu-nested-label">{option.icon}{option.name}</span>}
|
||||
disabled={option.disabled}
|
||||
menu={
|
||||
option.menuItems.map((menuOption, indx) => (
|
||||
<DropdownMenuItem
|
||||
key={menuOption.name}
|
||||
key={"callback-action-" + index + "-" + indx}
|
||||
className={menuOption.className}
|
||||
disabled={menuOption.disabled}
|
||||
onClick={(event) => handleMenuItemClick(event, menuOption.click)}
|
||||
>
|
||||
|
||||
+14
-11
@@ -11,7 +11,7 @@ import "ace-builds/src-noconflict/theme-monokai";
|
||||
import "ace-builds/src-noconflict/ext-searchbox";
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import {isValidHexColor, MythicColorSwatchInput} from '../../MythicComponents/MythicColorInput';
|
||||
import {getReadableTextColor, isValidHexColor, MythicColorSwatchInput} from '../../MythicComponents/MythicColorInput';
|
||||
|
||||
export function CallbacksTableEditDescriptionColorDialog(props) {
|
||||
const [comment, setComment] = React.useState("");
|
||||
@@ -42,7 +42,10 @@ export function CallbacksTableEditDescriptionColorDialog(props) {
|
||||
setColor(props.color);
|
||||
}, [props.description, props.color]);
|
||||
const previewColor = isValidHexColor(color) ? color : "";
|
||||
const previewBorder = previewColor === "" ? "1px dashed" : `2px solid ${previewColor}`;
|
||||
const darkPreviewBackground = previewColor || "#1f2937";
|
||||
const lightPreviewBackground = previewColor || "#f8fafc";
|
||||
const darkPreviewTextColor = previewColor ? getReadableTextColor(previewColor) : "#ffffff";
|
||||
const lightPreviewTextColor = previewColor ? getReadableTextColor(previewColor) : "#111827";
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">{props.title}</DialogTitle>
|
||||
@@ -77,7 +80,7 @@ export function CallbacksTableEditDescriptionColorDialog(props) {
|
||||
<Box sx={{display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, mb: 1}}>
|
||||
<Box sx={{minWidth: 0}}>
|
||||
<Typography variant="body2" sx={{fontWeight: 700}}>Callback Color</Typography>
|
||||
<Typography variant="caption" sx={{color: "text.secondary"}}>Callback row and tasking accent</Typography>
|
||||
<Typography variant="caption" sx={{color: "text.secondary"}}>Callback row background and tasking accent</Typography>
|
||||
</Box>
|
||||
<Box sx={{display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap", justifyContent: "flex-end"}}>
|
||||
<MythicColorSwatchInput
|
||||
@@ -98,12 +101,12 @@ export function CallbacksTableEditDescriptionColorDialog(props) {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderRadius: "6px",
|
||||
border: previewBorder,
|
||||
borderColor: previewColor === "" ? "divider" : previewColor,
|
||||
backgroundColor: "#1f2937",
|
||||
border: "1px solid",
|
||||
borderColor: "divider",
|
||||
backgroundColor: darkPreviewBackground,
|
||||
}}
|
||||
>
|
||||
<Typography sx={{color: "#ffffff", fontWeight: 700}}>Dark callback row</Typography>
|
||||
<Typography sx={{color: darkPreviewTextColor, fontWeight: 700}}>Dark callback row</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -112,12 +115,12 @@ export function CallbacksTableEditDescriptionColorDialog(props) {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderRadius: "6px",
|
||||
border: previewBorder,
|
||||
borderColor: previewColor === "" ? "divider" : previewColor,
|
||||
backgroundColor: "#f8fafc",
|
||||
border: "1px solid",
|
||||
borderColor: "divider",
|
||||
backgroundColor: lightPreviewBackground,
|
||||
}}
|
||||
>
|
||||
<Typography sx={{color: "#111827", fontWeight: 700}}>Light callback row</Typography>
|
||||
<Typography sx={{color: lightPreviewTextColor, fontWeight: 700}}>Light callback row</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -519,9 +519,14 @@ export const CallbacksTableSleepCell = React.memo( ({rowData, cellData, updateSl
|
||||
}, areEqual)
|
||||
export const CallbacksTableTagsCell = React.memo(({rowData, cellData}) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="mythic-callback-tagsCell">
|
||||
<ViewEditTags target_object={"callback_id"} target_object_id={rowData.id} />
|
||||
<TagsDisplay tags={cellData} expand={false} />
|
||||
<div className="mythic-callback-tagsList">
|
||||
{cellData?.length > 0 ?
|
||||
<TagsDisplay tags={cellData} expand={false} /> :
|
||||
<span className="mythic-callback-tagsEmpty">No tags</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, areEqual)
|
||||
|
||||
@@ -498,6 +498,26 @@ const TaskTagDisplay = ({task}) => {
|
||||
<TagsDisplay tags={task.tags} />
|
||||
)
|
||||
}
|
||||
const isTaskHeaderInteractiveClick = (e) => {
|
||||
const clickedInteractiveElement = e.target?.closest?.(
|
||||
`a, button, [role="button"], input, select, textarea`
|
||||
);
|
||||
return Boolean(clickedInteractiveElement && e.currentTarget.contains(clickedInteractiveElement));
|
||||
}
|
||||
const handleTaskHeaderClick = (e, onClick) => {
|
||||
if(window.getSelection().toString() !== ""){
|
||||
return;
|
||||
}
|
||||
if(isTaskHeaderInteractiveClick(e)){
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if(onClick){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onClick(e);
|
||||
}
|
||||
}
|
||||
const ColoredTaskDisplay = ({task, theme, children, expanded}) => {
|
||||
const themeColor = getTaskAccentColor(task, theme);
|
||||
return(
|
||||
@@ -529,11 +549,6 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
|
||||
e.stopPropagation();
|
||||
//e.preventDefault();
|
||||
}
|
||||
const onLocalClick = (e) => {
|
||||
if(onClick){
|
||||
onClick(e);
|
||||
}
|
||||
}
|
||||
const onClickKillIcon = (e, open) => {
|
||||
if(e){
|
||||
e.stopPropagation();
|
||||
@@ -542,7 +557,8 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
|
||||
}
|
||||
return (
|
||||
<ColoredTaskDisplay task={task} theme={theme} expanded={expanded} >
|
||||
<div id={taskDivID} className={compact ? `${classes.taskHeaderBody} ${classes.taskHeaderBodyCompact}` : classes.taskHeaderBody}>
|
||||
<div id={taskDivID} className={compact ? `${classes.taskHeaderBody} ${classes.taskHeaderBodyCompact}` : classes.taskHeaderBody}
|
||||
onClick={(e) => handleTaskHeaderClick(e, onClick)}>
|
||||
{displayComment && (
|
||||
<div className={classes.taskCommentBlock} onClick={preventPropagation}>
|
||||
<Typography component="div" sx={{fontSize: "0.72rem", fontWeight: 800, color: theme.palette.text.secondary, mb: 0.5}}>
|
||||
@@ -553,7 +569,7 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
<div className={classes.taskMetaRow} onClick={preventPropagation}>
|
||||
<div className={classes.taskMetaRow}>
|
||||
{taskingDisplayFields.map((fieldName) => (
|
||||
<TaskingMetadataField
|
||||
displayTimestamp={displayTimestamp}
|
||||
@@ -603,7 +619,7 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.taskCommandRow} onClick={onLocalClick}>
|
||||
<div className={classes.taskCommandRow}>
|
||||
{task.tasks.length > 0 &&
|
||||
<IconButton className={classes.taskChildToggle} size="small" onClick={toggleDisplayChildren}>
|
||||
{displayChildren ? <ExpandLessIcon fontSize="small" /> : <ExpandMoreIcon fontSize="small" />}
|
||||
@@ -999,6 +1015,7 @@ const TaskLabel = ({task, dropdownOpen, toggleTaskDropdown, me, newlyIssuedTasks
|
||||
<ColoredTaskLabel theme={theme} task={task} me={me} taskDivID={'scrolltotask' + task.id}
|
||||
displayChildren={displayChildren} toggleDisplayChildren={toggleDisplayChildren}
|
||||
expanded={dropdownOpen}
|
||||
onClick={toggleTaskDropdown}
|
||||
/>
|
||||
</StyledAccordionSummary>
|
||||
<TaskDisplayContainer key={task.id} me={me} task={task} />
|
||||
@@ -1191,10 +1208,6 @@ export const ColoredTaskLabelConsole = ({task, theme, me, taskDivID, onClick, di
|
||||
const [openKillTaskButton, setOpenKillTaskButton] = React.useState({open: false});
|
||||
const command = task?.command?.cmd || task.command_name;
|
||||
const commandLine = command + " " + task.display_params;
|
||||
const preventPropagation = (e) => {
|
||||
e.stopPropagation();
|
||||
//e.preventDefault();
|
||||
}
|
||||
const onClickKillIcon = (e, open) => {
|
||||
if(e){
|
||||
e.stopPropagation();
|
||||
@@ -1203,8 +1216,9 @@ export const ColoredTaskLabelConsole = ({task, theme, me, taskDivID, onClick, di
|
||||
}
|
||||
return (
|
||||
<ColoredTaskDisplayConsole task={task} theme={theme} expanded={expanded}>
|
||||
<div id={taskDivID} className={classes.taskHeaderBody}>
|
||||
<div className={classes.taskMetaRow} onClick={preventPropagation}>
|
||||
<div id={taskDivID} className={classes.taskHeaderBody}
|
||||
onClick={(e) => handleTaskHeaderClick(e, onClick)}>
|
||||
<div className={classes.taskMetaRow}>
|
||||
{taskingDisplayFields.map((fieldName) => (
|
||||
<TaskingMetadataField
|
||||
compactTimestamp
|
||||
|
||||
@@ -288,6 +288,13 @@ export const commandInParsedParameters = (cmd, parsedParameters) =>{
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
const getCaptureFileInputName = (parameterName, index) => {
|
||||
const safeName = String(parameterName || "file").replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toUpperCase();
|
||||
return `FILE_${safeName || "PARAMETER"}${index === undefined ? "" : "_" + (index + 1)}`;
|
||||
}
|
||||
const isSelectedFileObject = (value) => {
|
||||
return value !== undefined && value !== null && typeof value !== "string" && value.name !== undefined && value.name !== "";
|
||||
}
|
||||
export function TaskParametersDialog(props) {
|
||||
const [backdropOpen, setBackdropOpen] = React.useState(false);
|
||||
const [commandInfo, setCommandInfo] = useState({});
|
||||
@@ -839,6 +846,7 @@ export function TaskParametersDialog(props) {
|
||||
}, [selectedParameterGroup, rawParameters, loadedCommandsLoading, allCommandsLoading, loadedAllEdgesLoading, requiredPieces, loadedAllPayloadsLoading, loadedCredentialsLoading, loadedAllPayloadsOnHostsLoading, props.callback_id, props.choices]);
|
||||
const onSubmit = async () => {
|
||||
let newFileUUIDs = [];
|
||||
let capturedFiles = [];
|
||||
let collapsedParameters = {};
|
||||
for(const param of parameters){
|
||||
switch(param.type){
|
||||
@@ -863,6 +871,25 @@ export function TaskParametersDialog(props) {
|
||||
collapsedParameters[param.name] = param.value;
|
||||
break
|
||||
case "File":
|
||||
if(props.captureOnly){
|
||||
if(isSelectedFileObject(param.value)){
|
||||
const inputName = getCaptureFileInputName(param.name);
|
||||
capturedFiles.push({
|
||||
file: param.value,
|
||||
filename: param.value.name,
|
||||
index: 0,
|
||||
inputName,
|
||||
parameterName: param.name,
|
||||
});
|
||||
collapsedParameters[param.name] = inputName;
|
||||
}else if(typeof param.value === "string" && param.value.length > 0){
|
||||
collapsedParameters[param.name] = param.value;
|
||||
}else if(param.required){
|
||||
snackActions.warning("Missing file for " + param.display_name);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
const newUUID = await UploadTaskFile(param.value, "Uploaded as part of tasking");
|
||||
if(newUUID){
|
||||
if(newUUID !== "Missing file in form"){
|
||||
@@ -874,6 +901,30 @@ export function TaskParametersDialog(props) {
|
||||
}
|
||||
break;
|
||||
case "FileMultiple":
|
||||
if(props.captureOnly){
|
||||
let fileInputNames = [];
|
||||
for(let i = 0; i < param.value.length; i++){
|
||||
if(typeof param.value[i] === "string"){
|
||||
fileInputNames.push(param.value[i]);
|
||||
}else if(isSelectedFileObject(param.value[i])){
|
||||
const inputName = getCaptureFileInputName(param.name, i);
|
||||
capturedFiles.push({
|
||||
file: param.value[i],
|
||||
filename: param.value[i].name,
|
||||
index: i,
|
||||
inputName,
|
||||
parameterName: param.name,
|
||||
});
|
||||
fileInputNames.push(inputName);
|
||||
}
|
||||
}
|
||||
if(fileInputNames.length === 0 && param.required){
|
||||
snackActions.warning("Missing files for " + param.display_name);
|
||||
return;
|
||||
}
|
||||
collapsedParameters[param.name] = fileInputNames;
|
||||
break;
|
||||
}
|
||||
let fileIDs = [];
|
||||
for(let i = 0; i < param.value.length; i++){
|
||||
if(typeof param.value[i] === "string"){
|
||||
@@ -908,7 +959,10 @@ export function TaskParametersDialog(props) {
|
||||
}
|
||||
}
|
||||
setBackdropOpen(false);
|
||||
props.onSubmit(commandInfo.cmd, JSON.stringify(collapsedParameters), newFileUUIDs, selectedParameterGroup, commandInfo?.payloadtype?.name);
|
||||
props.onSubmit(commandInfo.cmd, JSON.stringify(collapsedParameters), props.captureOnly ? capturedFiles : newFileUUIDs, selectedParameterGroup, commandInfo?.payloadtype?.name, {
|
||||
capturedFiles,
|
||||
collapsedParameters,
|
||||
});
|
||||
|
||||
}
|
||||
const onAgentConnectAddNewPayloadOnHost = (host, payload) => {
|
||||
@@ -1038,7 +1092,7 @@ export function TaskParametersDialog(props) {
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={onSubmit} disabled={submenuOpenPreventTask} variant="contained" color="warning">
|
||||
Task
|
||||
{props.captureOnly ? "Use Parameters" : "Task"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -523,7 +523,6 @@ tspan {
|
||||
${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.11 : 0.07)},
|
||||
${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.11 : 0.07)}) !important;
|
||||
border-bottom-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.3 : 0.2)};
|
||||
color: ${(props) => props.theme.palette.text.primary} !important;
|
||||
}
|
||||
.MythicResizableGrid-cell.MythicResizableGrid-contextRow {
|
||||
background-image: linear-gradient(0deg,
|
||||
@@ -531,13 +530,22 @@ tspan {
|
||||
${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)}) !important;
|
||||
border-bottom-color: ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.52 : 0.34)};
|
||||
box-shadow: inset 0 1px 0 ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.52 : 0.34)};
|
||||
color: ${(props) => props.theme.palette.text.primary} !important;
|
||||
}
|
||||
.MythicResizableGrid-cell.selectedCallback {
|
||||
background-color: ${(props) => props.theme.table?.selected || props.theme.selectedCallbackColor + "CC"} !important;
|
||||
background-image: linear-gradient(0deg,
|
||||
${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.2 : 0.12)},
|
||||
${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.2 : 0.12)}) !important;
|
||||
border-bottom-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)};
|
||||
box-shadow: inset 0 1px 0 ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)},
|
||||
inset 0 -1px 0 ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)};
|
||||
}
|
||||
.MythicResizableGrid-cell.selectedCallbackHierarchy {
|
||||
background-color: ${(props) => props.theme.table?.selectedHierarchy || props.theme.selectedCallbackHierarchyColor + "CC"} !important;
|
||||
background-image: linear-gradient(0deg,
|
||||
${(props) => alpha(props.theme.palette.secondary.main, props.theme.palette.mode === "dark" ? 0.22 : 0.13)},
|
||||
${(props) => alpha(props.theme.palette.secondary.main, props.theme.palette.mode === "dark" ? 0.22 : 0.13)}) !important;
|
||||
border-bottom-color: ${(props) => alpha(props.theme.palette.secondary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)};
|
||||
box-shadow: inset 0 1px 0 ${(props) => alpha(props.theme.palette.secondary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)},
|
||||
inset 0 -1px 0 ${(props) => alpha(props.theme.palette.secondary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)};
|
||||
}
|
||||
.MythicResizableGrid-cell.MythicResizableGrid-rowInteractive {
|
||||
cursor: pointer !important;
|
||||
@@ -563,6 +571,16 @@ tspan {
|
||||
opacity: 1;
|
||||
width: 4px;
|
||||
}
|
||||
.MythicResizableGrid-cell.MythicResizableGrid-rowFirstCell.selectedCallback::before {
|
||||
background-color: ${(props) => props.theme.palette.primary.main};
|
||||
opacity: 1;
|
||||
width: 4px;
|
||||
}
|
||||
.MythicResizableGrid-cell.MythicResizableGrid-rowFirstCell.selectedCallbackHierarchy::before {
|
||||
background-color: ${(props) => props.theme.palette.secondary.main};
|
||||
opacity: 1;
|
||||
width: 4px;
|
||||
}
|
||||
.MythicResizableGrid-cell.MythicResizableGrid-rowLastCell {
|
||||
border-right-color: ${(props) => props.theme.table?.border || props.theme.borderColor};
|
||||
}
|
||||
@@ -617,6 +635,114 @@ tspan {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-callback-action-menu-header.MuiMenuItem-root,
|
||||
.mythic-callback-action-menu-header.MuiMenuItem-root.Mui-disabled {
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
padding: 0.55rem 0.8rem 0.45rem;
|
||||
}
|
||||
.mythic-callback-action-menu-label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.08rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-callback-action-menu-label-primary {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.82rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.mythic-callback-action-menu-label-secondary {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-callback-action-menu-section.MuiMenuItem-root,
|
||||
.mythic-callback-action-menu-section.MuiMenuItem-root.Mui-disabled {
|
||||
border-top: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
cursor: default;
|
||||
margin-top: 0.25rem;
|
||||
min-height: 26px;
|
||||
opacity: 1;
|
||||
padding: 0.45rem 0.8rem 0.18rem;
|
||||
}
|
||||
.mythic-callback-action-menu-section:first-of-type {
|
||||
border-top: 0;
|
||||
}
|
||||
.mythic-callback-action-menu-section-label {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.68rem;
|
||||
font-weight: 850;
|
||||
letter-spacing: 0.02em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.mythic-callback-action-menu-item.MuiMenuItem-root {
|
||||
align-items: center;
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.78rem;
|
||||
font-weight: 650;
|
||||
gap: 0;
|
||||
margin: 0.08rem 0.35rem;
|
||||
min-height: 34px;
|
||||
padding: 0.35rem 0.5rem;
|
||||
}
|
||||
.mythic-callback-action-menu-item.MuiMenuItem-root:hover,
|
||||
.mythic-callback-action-menu-item.MuiMenuItem-root[data-open="true"] {
|
||||
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.16 : 0.08)};
|
||||
}
|
||||
.mythic-callback-action-menu-nested-label {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
gap: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-callback-action-menu-icon {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.025)"};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.82rem;
|
||||
height: 22px;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
margin-right: 0.7rem;
|
||||
transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
|
||||
width: 22px;
|
||||
}
|
||||
.mythic-callback-action-menu-icon svg {
|
||||
display: block;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
.mythic-callback-action-menu-icon-primary {
|
||||
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.12 : 0.07)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.34 : 0.22)};
|
||||
color: ${(props) => props.theme.palette.primary.main};
|
||||
}
|
||||
.mythic-callback-action-menu-icon-warning {
|
||||
background-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.12 : 0.07)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.34 : 0.22)};
|
||||
color: ${(props) => props.theme.palette.warning.main};
|
||||
}
|
||||
.mythic-callback-action-menu-icon-error {
|
||||
background-color: ${(props) => alpha(props.theme.palette.error.main, props.theme.palette.mode === "dark" ? 0.12 : 0.07)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.error.main, props.theme.palette.mode === "dark" ? 0.34 : 0.22)};
|
||||
color: ${(props) => props.theme.palette.error.main};
|
||||
}
|
||||
.mythic-callback-action-menu-item.MuiMenuItem-root:hover .mythic-callback-action-menu-icon-neutral,
|
||||
.mythic-callback-action-menu-item.MuiMenuItem-root[data-open="true"] .mythic-callback-action-menu-icon-neutral {
|
||||
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.16 : 0.09)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.44 : 0.3)};
|
||||
color: ${(props) => props.theme.palette.primary.main};
|
||||
}
|
||||
.mythic-callback-iconButton.MuiIconButton-root {
|
||||
border: 1px solid transparent;
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
@@ -702,7 +828,7 @@ tspan {
|
||||
}
|
||||
.mythic-callback-displayId {
|
||||
align-items: center;
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
color: inherit;
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
font-weight: 700;
|
||||
@@ -768,6 +894,185 @@ tspan {
|
||||
display: block;
|
||||
font-size: inherit;
|
||||
}
|
||||
.mythic-callback-tagsCell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-callback-tagsCell .MuiIconButton-root {
|
||||
align-items: center !important;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.025)"} !important;
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.text.secondary} !important;
|
||||
display: inline-flex !important;
|
||||
flex: 0 0 auto;
|
||||
float: none !important;
|
||||
height: 22px;
|
||||
justify-content: center !important;
|
||||
padding: 0 !important;
|
||||
transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
|
||||
width: 22px;
|
||||
}
|
||||
.mythic-callback-tagsCell .MuiIconButton-root:hover {
|
||||
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.16 : 0.09)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.44 : 0.3)};
|
||||
color: ${(props) => props.theme.palette.primary.main} !important;
|
||||
}
|
||||
.mythic-callback-tagsCell .MuiIconButton-root svg {
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
.mythic-callback-tagsList {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.2rem;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-callback-tagsCell .MuiChip-root {
|
||||
border: 1px solid ${(props) => alpha(props.theme.palette.common.black, props.theme.palette.mode === "dark" ? 0.2 : 0.08)};
|
||||
flex: 0 0 auto;
|
||||
float: none !important;
|
||||
height: 18px !important;
|
||||
max-width: 5.5rem;
|
||||
}
|
||||
.mythic-callback-tagsCell .MuiChip-label {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
padding-left: 0.35rem;
|
||||
padding-right: 0.35rem;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.mythic-callback-tagsEmpty {
|
||||
color: ${(props) => props.theme.palette.text.disabled};
|
||||
font-size: 0.72rem;
|
||||
font-weight: 650;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-c2-path-title.MuiDialogTitle-root {
|
||||
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.paper};
|
||||
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
padding: 0.85rem 1rem;
|
||||
}
|
||||
.mythic-c2-path-title-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-c2-path-title-copy {
|
||||
min-width: min(100%, 18rem);
|
||||
}
|
||||
.mythic-c2-path-title-text.MuiTypography-root {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 1rem;
|
||||
font-weight: 850;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.mythic-c2-path-title-subtitle.MuiTypography-root {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.35;
|
||||
margin-top: 0.18rem;
|
||||
}
|
||||
.mythic-c2-path-summary {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.mythic-c2-path-summary-chip {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.025)"};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.7rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
padding: 0.32rem 0.45rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-c2-path-content.MuiDialogContent-root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
height: min(72vh, 820px);
|
||||
min-height: 28rem;
|
||||
padding: 0.85rem;
|
||||
}
|
||||
.mythic-c2-path-toolbar {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.paper};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
padding: 0.7rem;
|
||||
}
|
||||
.mythic-c2-path-toolbar-copy {
|
||||
min-width: min(100%, 14rem);
|
||||
}
|
||||
.mythic-c2-path-toolbar-title.MuiTypography-root {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.84rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.mythic-c2-path-toolbar-description.MuiTypography-root {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.74rem;
|
||||
line-height: 1.35;
|
||||
margin-top: 0.12rem;
|
||||
}
|
||||
.mythic-c2-path-controls {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1 1 24rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.65rem;
|
||||
justify-content: flex-end;
|
||||
min-width: min(100%, 18rem);
|
||||
}
|
||||
.mythic-c2-path-control.MuiFormControl-root {
|
||||
min-width: 11rem;
|
||||
}
|
||||
.mythic-c2-path-control-wide.MuiFormControl-root {
|
||||
min-width: min(100%, 20rem);
|
||||
}
|
||||
.mythic-c2-path-canvas {
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-c2-path-canvas .react-flow__controls {
|
||||
box-shadow: none;
|
||||
}
|
||||
.mythic-c2-path-canvas .react-flow__controls-button {
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
}
|
||||
.mythic-c2-path-canvas .react-flow__controls-button:hover {
|
||||
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.16 : 0.08)};
|
||||
color: ${(props) => props.theme.palette.primary.main};
|
||||
}
|
||||
.mythic-callback-trigger-summary {
|
||||
align-items: flex-start;
|
||||
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.paper};
|
||||
@@ -4083,9 +4388,29 @@ tspan {
|
||||
display: inline-flex;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 750;
|
||||
gap: 0.2rem;
|
||||
line-height: 1.2;
|
||||
min-height: 1.45rem;
|
||||
padding: 0 0.45rem;
|
||||
padding: 0 0.18rem 0 0.45rem;
|
||||
}
|
||||
.mythic-eventing-file-chip-name {
|
||||
max-width: 14rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-eventing-file-chip-remove.MuiIconButton-root {
|
||||
color: ${(props) => props.theme.palette.text.secondary} !important;
|
||||
height: 1.15rem;
|
||||
padding: 0;
|
||||
width: 1.15rem;
|
||||
}
|
||||
.mythic-eventing-file-chip-remove.MuiIconButton-root:hover {
|
||||
background-color: ${(props) => props.theme.palette.error.main + "18"} !important;
|
||||
color: ${(props) => props.theme.palette.error.main} !important;
|
||||
}
|
||||
.mythic-eventing-file-chip-remove svg {
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
.mythic-eventing-metadata-editor {
|
||||
min-height: 16rem;
|
||||
@@ -4607,6 +4932,7 @@ tspan {
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.warning.main};
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 0.64rem;
|
||||
font-weight: 850;
|
||||
line-height: 1;
|
||||
@@ -4627,6 +4953,49 @@ tspan {
|
||||
.mythic-eventing-action-data-control .MuiTextField-root {
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-eventing-task-create-command-row {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-task-create-command-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.mythic-eventing-task-helper-summary {
|
||||
background-color: ${(props) => props.theme.palette.info.main + "10"};
|
||||
border: 1px solid ${(props) => props.theme.palette.info.main + "2f"};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.76rem;
|
||||
font-weight: 650;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 0.65rem;
|
||||
padding: 0.55rem 0.65rem;
|
||||
}
|
||||
.mythic-eventing-task-helper-filter {
|
||||
margin-bottom: 0.65rem;
|
||||
}
|
||||
.mythic-eventing-task-helper-table {
|
||||
height: min(50vh, 34rem);
|
||||
min-height: 18rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.mythic-eventing-task-helper-preview {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
display: -webkit-box;
|
||||
font-size: 0.74rem;
|
||||
line-height: 1.35;
|
||||
max-height: 2.1rem;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
.mythic-eventing-action-array-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -2,6 +2,8 @@ package webcontroller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/its-a-feature/Mythic/database"
|
||||
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
|
||||
@@ -9,7 +11,6 @@ import (
|
||||
"github.com/its-a-feature/Mythic/logging"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type EventingExportInput struct {
|
||||
@@ -90,9 +91,11 @@ func getFormattedEventingFile(eventGroup *databaseStructs.EventGroup, includeSte
|
||||
RunAs: eventGroup.RunAs,
|
||||
}
|
||||
if includeSteps {
|
||||
err := database.DB.Select(&eventGroup.Steps, `SELECT * FROM eventstep WHERE eventgroup_id=$1`, eventGroup.ID)
|
||||
if err != nil {
|
||||
logging.LogError(err, "failed to get eventgroup steps")
|
||||
if len(eventGroup.Steps) == 0 {
|
||||
err := database.DB.Select(&eventGroup.Steps, `SELECT * FROM eventstep WHERE eventgroup_id=$1`, eventGroup.ID)
|
||||
if err != nil {
|
||||
logging.LogError(err, "failed to get eventgroup steps")
|
||||
}
|
||||
}
|
||||
for _, step := range eventGroup.Steps {
|
||||
exportedEventGroup.Steps = append(exportedEventGroup.Steps, eventing.EventStep{
|
||||
|
||||
Reference in New Issue
Block a user