Skip to content

Commit 3e61149

Browse files
committed
First set of draggable modals to access content behind them
1 parent 3796640 commit 3e61149

File tree

16 files changed

+159
-93
lines changed

16 files changed

+159
-93
lines changed

MythicReactUI/CHANGELOG.MD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.3.104] - 2026-03-19
8+
9+
### Changed
10+
11+
- Added ability to drag and move some modals and copy data from behind them
12+
713
## [0.3.103] - 2026-03-17
814

915
### Changed

MythicReactUI/src/components/MythicComponents/MythicDialog.js

Lines changed: 83 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,27 @@ import {useTheme} from '@mui/material/styles';
2121
import WrapTextIcon from '@mui/icons-material/WrapText';
2222
import {IconButton} from '@mui/material';
2323
import {MythicStyledTooltip} from "./MythicStyledTooltip";
24+
import Draggable from 'react-draggable';
25+
import {MythicDraggableDialogTitle} from "./MythicDraggableDialogTitle";
26+
import {GetShortRandomString} from "./MythicResizableGrid/MythicResizableGrid";
2427

2528
export function MythicDialog(props) {
26-
const descriptionElementRef = React.useRef(null);
27-
React.useEffect(() => {
29+
const [draggedState, setDraggedState] = React.useState({
30+
style: {},
31+
paperStyle: {
32+
margin: "0",
33+
height: "fit-content",
34+
width: "stretch"
35+
},
36+
containerStyle: {
37+
38+
},
39+
hideBackdrop: false,
40+
modified: false
41+
});
42+
const nodeRef = React.useRef(null);
43+
const descriptionElementRef = React.useRef(null);
44+
React.useEffect(() => {
2845
if (props.open) {
2946
const { current: descriptionElement } = descriptionElementRef;
3047
if (descriptionElement !== null) {
@@ -35,28 +52,73 @@ export function MythicDialog(props) {
3552
const dialogOnClick = (e) => {
3653
e.stopPropagation();
3754
if(e.target.classList.length > 0 && e.target.classList.contains("MuiDialog-container")){
38-
props.onClose();
55+
//props.onClose();
3956
}
4057
}
4158
const dialogOnContextMenu = (e) => {
4259
e.stopPropagation();
43-
60+
}
61+
const handleOnClose = (event, reason) => {
62+
if(reason === "backdropClick" && draggedState.hideBackdrop){
63+
return;
64+
}
65+
props.onClose();
66+
}
67+
const onStart = (e) => {
68+
if(!draggedState.modified){
69+
setDraggedState({
70+
style: {
71+
width: e.target.parentElement.offsetWidth + "px",
72+
height: e.target.offsetParent.offsetHeight + "px",
73+
margin: "auto",
74+
},
75+
paperStyle: {
76+
height: e.target.offsetParent.offsetHeight + "px",
77+
width: e.target.parentElement.offsetWidth + "px",
78+
margin: 0
79+
},
80+
containerStyle: {
81+
height: "fit-content"
82+
},
83+
hideBackdrop: true,
84+
modified: true,
85+
})
86+
}
4487
}
4588
return (
46-
<Dialog
47-
open={props.open}
48-
onClose={props.onClose}
49-
scroll="paper"
50-
maxWidth={props.maxWidth}
51-
fullWidth={props.fullWidth}
52-
style={props.style}
53-
aria-labelledby="scroll-dialog-title"
54-
aria-describedby="scroll-dialog-description"
55-
onMouseDown={dialogOnClick}
56-
onContextMenu={dialogOnContextMenu}
89+
<Draggable
90+
nodeRef={nodeRef}
91+
handle="#mythic-draggable-title"
92+
cancel={'[class*="MuiDialogContent-root"]'}
93+
onStart={onStart}
5794
>
58-
{props.innerDialog}
59-
</Dialog>
95+
<Dialog
96+
ref={nodeRef}
97+
open={props.open}
98+
onClose={handleOnClose}
99+
scroll="paper"
100+
maxWidth={props.maxWidth}
101+
fullWidth={true}
102+
style={{...props.style, ...draggedState.style}}
103+
disableEnforceFocus={true}
104+
disablePortal={true}
105+
hideBackdrop={draggedState.hideBackdrop}
106+
aria-labelledby="scroll-dialog-title"
107+
aria-describedby="scroll-dialog-description"
108+
sx={{
109+
[".MuiPaper-root"]: {
110+
...draggedState.paperStyle
111+
},
112+
[".MuiDialog-container"]: {
113+
...draggedState.containerStyle
114+
}
115+
}}
116+
onMouseDown={dialogOnClick}
117+
onContextMenu={dialogOnContextMenu}
118+
>
119+
{props.innerDialog}
120+
</Dialog>
121+
</Draggable>
60122
);
61123
}
62124

@@ -85,17 +147,16 @@ export function MythicModifyStringDialog(props) {
85147
return (
86148
<React.Fragment>
87149
{props.title !== "" &&
88-
<DialogTitle id="form-dialog-title">{props.title}
150+
<MythicDraggableDialogTitle>{props.title}
89151
<MythicStyledTooltip title={wrap ? "Toggle off word wrap" : "Toggl on word wrap"}
90152
tooltipStyle={{float: "right"}}>
91153
<IconButton onClick={() => {setWrap(!wrap)}}>
92154
<WrapTextIcon color={wrap ? "success" : "secondary"} />
93155
</IconButton>
94156
</MythicStyledTooltip>
95-
96-
</DialogTitle>
157+
</MythicDraggableDialogTitle>
97158
}
98-
<DialogContent dividers={true} style={{height: "100%", margin: 0, padding: 0}}>
159+
<DialogContent dividers={true} style={{ margin: 0, padding: 0}}>
99160
<AceEditor
100161
mode="json"
101162
theme={theme.palette.mode === 'dark' ? 'monokai' : 'github'}
@@ -198,7 +259,7 @@ export function MythicViewJSONAsTableDialog(props) {
198259
}, [props.value, props.leftColumn, props.rightColumn]);
199260
return (
200261
<React.Fragment>
201-
<DialogTitle id="form-dialog-title" style={{wordBreak: "break-all", maxWidth: "100%"}}>{props.title}</DialogTitle>
262+
<MythicDraggableDialogTitle style={{wordBreak: "break-all", maxWidth: "100%"}}>{props.title}</MythicDraggableDialogTitle>
202263

203264
<TableContainer className="mythicElement" style={{paddingLeft: "10px"}}>
204265
<Table size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "scroll"}}>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import DialogTitle from '@mui/material/DialogTitle';
2+
3+
4+
export const MythicDraggableDialogTitle = ({children}) => {
5+
return (
6+
<DialogTitle id="mythic-draggable-title" style={{ cursor: 'move', width: "100%" }}>
7+
{children}
8+
</DialogTitle>
9+
)
10+
}

MythicReactUI/src/components/MythicComponents/MythicTag.js

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import Typography from '@mui/material/Typography';
2929
import MythicStyledTableCell from "./MythicTableCell";
3030
import {meState} from "../../cache";
3131
import { useReactiveVar } from '@apollo/client';
32+
import {MythicDraggableDialogTitle} from "./MythicDraggableDialogTitle";
3233

3334
const createNewTagMutationTemplate = ({target_object}) => {
3435
// target_object should be something like "task_id"
@@ -495,20 +496,17 @@ const onAcceptDelete = () => {
495496

496497
return (
497498
<React.Fragment>
498-
<DialogTitle id="form-dialog-title">Edit Tags
499-
</DialogTitle>
500-
<DialogContent dividers={true}>
501-
{openNewDialog ?
502-
(<MythicDialog fullWidth={true} maxWidth="xl" open={openNewDialog}
499+
<MythicDraggableDialogTitle >Edit Tags </MythicDraggableDialogTitle>
500+
<DialogContent dividers={true} style={{width: "100%"}}>
501+
{openNewDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openNewDialog}
503502
onClose={()=>{setOpenNewDialog(false);}}
504503
innerDialog={<NewTagDialog me={props.me}
505504
target_object={props.target_object}
506505
target_object_id={props.target_object_id}
507506
onClose={()=>{setOpenNewDialog(false);}}
508507
onSubmit={handleNewTagCreate} />}
509-
/>) : null}
510-
<TableContainer className="mythicElement">
511-
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
508+
/>}
509+
<Table size="small" style={{ width: "100%", overflow: "scroll"}}>
512510
<TableBody>
513511
<TableRow hover>
514512
<MythicStyledTableCell style={{width: "30%"}}>Select Existing Tag to Edit or Add New</MythicStyledTableCell>
@@ -578,7 +576,6 @@ return (
578576
</TableRow>
579577
</TableBody>
580578
</Table>
581-
</TableContainer>
582579
</DialogContent>
583580
<DialogActions>
584581
<Button onClick={props.onClose} variant="contained" color="primary">
@@ -647,10 +644,9 @@ export function NewTagDialog(props) {
647644

648645
return (
649646
<React.Fragment>
650-
<DialogTitle id="form-dialog-title">Add New Tag</DialogTitle>
651-
<DialogContent dividers={true}>
652-
<TableContainer className="mythicElement">
653-
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
647+
<MythicDraggableDialogTitle >Add New Tag</MythicDraggableDialogTitle>
648+
<DialogContent dividers={true} style={{width: "100%"}}>
649+
<Table size="small" style={{ overflow: "scroll", width: "100%"}}>
654650
<TableBody>
655651
<TableRow hover>
656652
<MythicStyledTableCell style={{width: "20%"}}>
@@ -717,7 +713,6 @@ export function NewTagDialog(props) {
717713
</TableRow>
718714
</TableBody>
719715
</Table>
720-
</TableContainer>
721716
</DialogContent>
722717
<DialogActions>
723718
<Button onClick={props.onClose} variant="contained" color="primary">

MythicReactUI/src/components/pages/Callbacks/AddRemoveCallbackCommandsDialog.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import CardHeader from '@mui/material/CardHeader';
1212
import {gql, useQuery} from '@apollo/client';
1313
import { CardContent } from '@mui/material';
1414
import {classes, StyledButton, StyledDivider} from '../../MythicComponents/MythicTransferList';
15+
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
1516

1617
const getCommandsQuery = gql`
1718
query GetCallbackDetails($callback_id: Int!) {
@@ -184,7 +185,7 @@ export function AddRemoveCallbackCommandsDialog(props) {
184185
}
185186
return (
186187
<React.Fragment>
187-
<DialogTitle id="form-dialog-title">Add or Remove Commands for Callback {props.display_id} </DialogTitle>
188+
<MythicDraggableDialogTitle>Add or Remove Commands for Callback {props.display_id} </MythicDraggableDialogTitle>
188189
<DialogContent dividers={true} style={{height: "100%", display: "flex", flexDirection: "column", position: "relative", maxHeight: "100%"}}>
189190
This will add or remove commands associated with this callback from Mythic's perspective.
190191
This does NOT add or remove commands within the payload itself that's beaconing out to Mythic.

MythicReactUI/src/components/pages/Callbacks/TaskDisplayContainer.js

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -288,41 +288,36 @@ const SideDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput,
288288
}
289289
return (
290290
<div style={{height: "100%"}}>
291-
{openTaskTagDialog ?
292-
(<MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
291+
{openTaskTagDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
293292
onClose={()=>{setOpenTaskTagDialog(false);}}
294293
innerDialog={<ViewEditTagsDialog me={me} target_object={"task_id"} target_object_id={task.id} onClose={()=>{setOpenTaskTagDialog(false);}} />}
295-
/>) : null}
296-
{openCommentDialog ?
297-
(<MythicDialog fullWidth={true} maxWidth="lg" open={openCommentDialog}
294+
/>
295+
}
296+
{openCommentDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openCommentDialog}
298297
onClose={()=>{setOpenCommentDialog(false);}}
299298
innerDialog={<TaskCommentDialog task_id={task.id} onClose={()=>{setOpenCommentDialog(false);}} />}
300-
/>) : null
299+
/>
301300
}
302-
{openParametersDialog ?
303-
(<MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
301+
{openParametersDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
304302
onClose={()=>{setOpenParametersDialog(false);}}
305303
innerDialog={<TaskViewParametersDialog task_id={task.id} onClose={()=>{setOpenParametersDialog(false);}} />}
306-
/>) : null
304+
/>
307305
}
308-
{openTokenDialog ?
309-
(<MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
306+
{openTokenDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
310307
onClose={()=>{setOpenTokenDialog(false);}}
311308
innerDialog={<TaskTokenDialog token_id={task.token === undefined ? 0 : task.token.id} onClose={()=>{setOpenTokenDialog(false);}} />}
312-
/>) : null
309+
/>
313310
}
314-
{openOpsecDialog.open ?
315-
(<MythicDialog fullWidth={true} maxWidth="md" open={openOpsecDialog.open}
311+
{openOpsecDialog.open && <MythicDialog fullWidth={true} maxWidth="lg" open={openOpsecDialog.open}
316312
onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}}
317313
innerDialog={<TaskOpsecDialog task_id={task.id} view={openOpsecDialog.view} onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}} />}
318-
/>) : null
314+
/>
319315
}
320316

321-
{openStdoutStderrDialog ?
322-
(<MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
317+
{openStdoutStderrDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
323318
onClose={()=>{setOpenStdoutStderrDialog(false);}}
324319
innerDialog={<TaskViewStdoutStderrDialog task_id={task.id} onClose={()=>{setOpenStdoutStderrDialog(false);}} />}
325-
/>) : null
320+
/>
326321
}
327322
{openEventingDialog &&
328323
<MythicDialog
@@ -562,41 +557,36 @@ const SpeedDialDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput
562557
return (
563558
<React.Fragment>
564559
<Backdrop open={openSpeedDial} onClick={()=>{setOpenSpeedDial(false);}} style={{zIndex: 2, position: "absolute"}}/>
565-
{openTaskTagDialog ?
566-
(<MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
560+
{openTaskTagDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
567561
onClose={()=>{setOpenTaskTagDialog(false);}}
568562
innerDialog={<ViewEditTagsDialog me={me} target_object={"task_id"} target_object_id={task.id} onClose={()=>{setOpenTaskTagDialog(false);}} />}
569-
/>) : null}
570-
{openCommentDialog ?
571-
(<MythicDialog fullWidth={true} maxWidth="lg" open={openCommentDialog}
563+
/>
564+
}
565+
{openCommentDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openCommentDialog}
572566
onClose={()=>{setOpenCommentDialog(false);}}
573567
innerDialog={<TaskCommentDialog task_id={task.id} onClose={()=>{setOpenCommentDialog(false);}} />}
574-
/>) : null
568+
/>
575569
}
576-
{openParametersDialog ?
577-
(<MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
570+
{openParametersDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
578571
onClose={()=>{setOpenParametersDialog(false);}}
579572
innerDialog={<TaskViewParametersDialog task_id={task.id} onClose={()=>{setOpenParametersDialog(false);}} />}
580-
/>) : null
573+
/>
581574
}
582-
{openTokenDialog ?
583-
(<MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
575+
{openTokenDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
584576
onClose={()=>{setOpenTokenDialog(false);}}
585577
innerDialog={<TaskTokenDialog token_id={task.token === undefined ? 0 : task.token.id} onClose={()=>{setOpenTokenDialog(false);}} />}
586-
/>) : null
578+
/>
587579
}
588-
{openOpsecDialog.open ?
589-
(<MythicDialog fullWidth={true} maxWidth="md" open={openOpsecDialog.open}
580+
{openOpsecDialog.open && <MythicDialog fullWidth={true} maxWidth="lg" open={openOpsecDialog.open}
590581
onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}}
591582
innerDialog={<TaskOpsecDialog task_id={task.id} view={openOpsecDialog.view} onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}} />}
592-
/>) : null
583+
/>
593584
}
594585

595-
{openStdoutStderrDialog ?
596-
(<MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
586+
{openStdoutStderrDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
597587
onClose={()=>{setOpenStdoutStderrDialog(false);}}
598588
innerDialog={<TaskViewStdoutStderrDialog task_id={task.id} onClose={()=>{setOpenStdoutStderrDialog(false);}} />}
599-
/>) : null
589+
/>
600590
}
601591
{openEventingDialog &&
602592
<MythicDialog

MythicReactUI/src/components/pages/Callbacks/TaskParametersDialog.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {Backdrop, CircularProgress} from '@mui/material';
2121
import Divider from '@mui/material/Divider';
2222
import {b64DecodeUnicode} from './ResponseDisplay';
2323
import {snackActions} from "../../utilities/Snackbar";
24+
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
2425

2526
//if we need to get all the loaded commands for the callback and filter, use this
2627
const GetLoadedCommandsQuery = gql`
@@ -977,7 +978,7 @@ export function TaskParametersDialog(props) {
977978
}
978979
return (
979980
<React.Fragment>
980-
<DialogTitle id="form-dialog-title">{commandInfo.cmd}'s Parameters</DialogTitle>
981+
<MythicDraggableDialogTitle>{commandInfo.cmd}'s Parameters</MythicDraggableDialogTitle>
981982
<DialogContent dividers={true}>
982983
<Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute"}}>
983984
<CircularProgress color="inherit" />

MythicReactUI/src/components/pages/Callbacks/TaskTokenDialog.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import TableCell from '@mui/material/TableCell';
1010
import TableContainer from '@mui/material/TableContainer';
1111
import TableHead from '@mui/material/TableHead';
1212
import TableRow from '@mui/material/TableRow';
13-
import Paper from '@mui/material/Paper';
13+
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
1414

1515
export const allTokenDataFragment = gql`
1616
fragment allTokenData on token {
@@ -89,7 +89,7 @@ export function TaskTokenDialog(props) {
8989
});
9090
return (
9191
<React.Fragment>
92-
<DialogTitle id="form-dialog-title">Token Information</DialogTitle>
92+
<MythicDraggableDialogTitle >Token Information</MythicDraggableDialogTitle>
9393
<TableContainer className="mythicElement">
9494
<Table size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "scroll"}}>
9595
<TableHead>

0 commit comments

Comments
 (0)