First set of draggable modals to access content behind them

This commit is contained in:
its-a-feature
2026-03-19 19:05:33 -05:00
parent 3796640835
commit 3e611494cc
16 changed files with 159 additions and 93 deletions
+6
View File
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.104] - 2026-03-19
### Changed
- Added ability to drag and move some modals and copy data from behind them
## [0.3.103] - 2026-03-17
### Changed
@@ -21,10 +21,27 @@ import {useTheme} from '@mui/material/styles';
import WrapTextIcon from '@mui/icons-material/WrapText';
import {IconButton} from '@mui/material';
import {MythicStyledTooltip} from "./MythicStyledTooltip";
import Draggable from 'react-draggable';
import {MythicDraggableDialogTitle} from "./MythicDraggableDialogTitle";
import {GetShortRandomString} from "./MythicResizableGrid/MythicResizableGrid";
export function MythicDialog(props) {
const descriptionElementRef = React.useRef(null);
React.useEffect(() => {
const [draggedState, setDraggedState] = React.useState({
style: {},
paperStyle: {
margin: "0",
height: "fit-content",
width: "stretch"
},
containerStyle: {
},
hideBackdrop: false,
modified: false
});
const nodeRef = React.useRef(null);
const descriptionElementRef = React.useRef(null);
React.useEffect(() => {
if (props.open) {
const { current: descriptionElement } = descriptionElementRef;
if (descriptionElement !== null) {
@@ -35,28 +52,73 @@ export function MythicDialog(props) {
const dialogOnClick = (e) => {
e.stopPropagation();
if(e.target.classList.length > 0 && e.target.classList.contains("MuiDialog-container")){
props.onClose();
//props.onClose();
}
}
const dialogOnContextMenu = (e) => {
e.stopPropagation();
}
const handleOnClose = (event, reason) => {
if(reason === "backdropClick" && draggedState.hideBackdrop){
return;
}
props.onClose();
}
const onStart = (e) => {
if(!draggedState.modified){
setDraggedState({
style: {
width: e.target.parentElement.offsetWidth + "px",
height: e.target.offsetParent.offsetHeight + "px",
margin: "auto",
},
paperStyle: {
height: e.target.offsetParent.offsetHeight + "px",
width: e.target.parentElement.offsetWidth + "px",
margin: 0
},
containerStyle: {
height: "fit-content"
},
hideBackdrop: true,
modified: true,
})
}
}
return (
<Dialog
open={props.open}
onClose={props.onClose}
scroll="paper"
maxWidth={props.maxWidth}
fullWidth={props.fullWidth}
style={props.style}
aria-labelledby="scroll-dialog-title"
aria-describedby="scroll-dialog-description"
onMouseDown={dialogOnClick}
onContextMenu={dialogOnContextMenu}
<Draggable
nodeRef={nodeRef}
handle="#mythic-draggable-title"
cancel={'[class*="MuiDialogContent-root"]'}
onStart={onStart}
>
{props.innerDialog}
</Dialog>
<Dialog
ref={nodeRef}
open={props.open}
onClose={handleOnClose}
scroll="paper"
maxWidth={props.maxWidth}
fullWidth={true}
style={{...props.style, ...draggedState.style}}
disableEnforceFocus={true}
disablePortal={true}
hideBackdrop={draggedState.hideBackdrop}
aria-labelledby="scroll-dialog-title"
aria-describedby="scroll-dialog-description"
sx={{
[".MuiPaper-root"]: {
...draggedState.paperStyle
},
[".MuiDialog-container"]: {
...draggedState.containerStyle
}
}}
onMouseDown={dialogOnClick}
onContextMenu={dialogOnContextMenu}
>
{props.innerDialog}
</Dialog>
</Draggable>
);
}
@@ -85,17 +147,16 @@ export function MythicModifyStringDialog(props) {
return (
<React.Fragment>
{props.title !== "" &&
<DialogTitle id="form-dialog-title">{props.title}
<MythicDraggableDialogTitle>{props.title}
<MythicStyledTooltip title={wrap ? "Toggle off word wrap" : "Toggl on word wrap"}
tooltipStyle={{float: "right"}}>
<IconButton onClick={() => {setWrap(!wrap)}}>
<WrapTextIcon color={wrap ? "success" : "secondary"} />
</IconButton>
</MythicStyledTooltip>
</DialogTitle>
</MythicDraggableDialogTitle>
}
<DialogContent dividers={true} style={{height: "100%", margin: 0, padding: 0}}>
<DialogContent dividers={true} style={{ margin: 0, padding: 0}}>
<AceEditor
mode="json"
theme={theme.palette.mode === 'dark' ? 'monokai' : 'github'}
@@ -198,7 +259,7 @@ export function MythicViewJSONAsTableDialog(props) {
}, [props.value, props.leftColumn, props.rightColumn]);
return (
<React.Fragment>
<DialogTitle id="form-dialog-title" style={{wordBreak: "break-all", maxWidth: "100%"}}>{props.title}</DialogTitle>
<MythicDraggableDialogTitle style={{wordBreak: "break-all", maxWidth: "100%"}}>{props.title}</MythicDraggableDialogTitle>
<TableContainer className="mythicElement" style={{paddingLeft: "10px"}}>
<Table size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "scroll"}}>
@@ -0,0 +1,10 @@
import DialogTitle from '@mui/material/DialogTitle';
export const MythicDraggableDialogTitle = ({children}) => {
return (
<DialogTitle id="mythic-draggable-title" style={{ cursor: 'move', width: "100%" }}>
{children}
</DialogTitle>
)
}
@@ -29,6 +29,7 @@ import Typography from '@mui/material/Typography';
import MythicStyledTableCell from "./MythicTableCell";
import {meState} from "../../cache";
import { useReactiveVar } from '@apollo/client';
import {MythicDraggableDialogTitle} from "./MythicDraggableDialogTitle";
const createNewTagMutationTemplate = ({target_object}) => {
// target_object should be something like "task_id"
@@ -495,20 +496,17 @@ const onAcceptDelete = () => {
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">Edit Tags
</DialogTitle>
<DialogContent dividers={true}>
{openNewDialog ?
(<MythicDialog fullWidth={true} maxWidth="xl" open={openNewDialog}
<MythicDraggableDialogTitle >Edit Tags </MythicDraggableDialogTitle>
<DialogContent dividers={true} style={{width: "100%"}}>
{openNewDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openNewDialog}
onClose={()=>{setOpenNewDialog(false);}}
innerDialog={<NewTagDialog me={props.me}
target_object={props.target_object}
target_object_id={props.target_object_id}
onClose={()=>{setOpenNewDialog(false);}}
onSubmit={handleNewTagCreate} />}
/>) : null}
<TableContainer className="mythicElement">
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
/>}
<Table size="small" style={{ width: "100%", overflow: "scroll"}}>
<TableBody>
<TableRow hover>
<MythicStyledTableCell style={{width: "30%"}}>Select Existing Tag to Edit or Add New</MythicStyledTableCell>
@@ -578,7 +576,6 @@ return (
</TableRow>
</TableBody>
</Table>
</TableContainer>
</DialogContent>
<DialogActions>
<Button onClick={props.onClose} variant="contained" color="primary">
@@ -647,10 +644,9 @@ export function NewTagDialog(props) {
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">Add New Tag</DialogTitle>
<DialogContent dividers={true}>
<TableContainer className="mythicElement">
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
<MythicDraggableDialogTitle >Add New Tag</MythicDraggableDialogTitle>
<DialogContent dividers={true} style={{width: "100%"}}>
<Table size="small" style={{ overflow: "scroll", width: "100%"}}>
<TableBody>
<TableRow hover>
<MythicStyledTableCell style={{width: "20%"}}>
@@ -717,7 +713,6 @@ export function NewTagDialog(props) {
</TableRow>
</TableBody>
</Table>
</TableContainer>
</DialogContent>
<DialogActions>
<Button onClick={props.onClose} variant="contained" color="primary">
@@ -12,6 +12,7 @@ import CardHeader from '@mui/material/CardHeader';
import {gql, useQuery} from '@apollo/client';
import { CardContent } from '@mui/material';
import {classes, StyledButton, StyledDivider} from '../../MythicComponents/MythicTransferList';
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
const getCommandsQuery = gql`
query GetCallbackDetails($callback_id: Int!) {
@@ -184,7 +185,7 @@ export function AddRemoveCallbackCommandsDialog(props) {
}
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">Add or Remove Commands for Callback {props.display_id} </DialogTitle>
<MythicDraggableDialogTitle>Add or Remove Commands for Callback {props.display_id} </MythicDraggableDialogTitle>
<DialogContent dividers={true} style={{height: "100%", display: "flex", flexDirection: "column", position: "relative", maxHeight: "100%"}}>
This will add or remove commands associated with this callback from Mythic's perspective.
This does NOT add or remove commands within the payload itself that's beaconing out to Mythic.
@@ -288,41 +288,36 @@ const SideDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput,
}
return (
<div style={{height: "100%"}}>
{openTaskTagDialog ?
(<MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
{openTaskTagDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
onClose={()=>{setOpenTaskTagDialog(false);}}
innerDialog={<ViewEditTagsDialog me={me} target_object={"task_id"} target_object_id={task.id} onClose={()=>{setOpenTaskTagDialog(false);}} />}
/>) : null}
{openCommentDialog ?
(<MythicDialog fullWidth={true} maxWidth="lg" open={openCommentDialog}
/>
}
{openCommentDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openCommentDialog}
onClose={()=>{setOpenCommentDialog(false);}}
innerDialog={<TaskCommentDialog task_id={task.id} onClose={()=>{setOpenCommentDialog(false);}} />}
/>) : null
/>
}
{openParametersDialog ?
(<MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
{openParametersDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
onClose={()=>{setOpenParametersDialog(false);}}
innerDialog={<TaskViewParametersDialog task_id={task.id} onClose={()=>{setOpenParametersDialog(false);}} />}
/>) : null
/>
}
{openTokenDialog ?
(<MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
{openTokenDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
onClose={()=>{setOpenTokenDialog(false);}}
innerDialog={<TaskTokenDialog token_id={task.token === undefined ? 0 : task.token.id} onClose={()=>{setOpenTokenDialog(false);}} />}
/>) : null
/>
}
{openOpsecDialog.open ?
(<MythicDialog fullWidth={true} maxWidth="md" open={openOpsecDialog.open}
{openOpsecDialog.open && <MythicDialog fullWidth={true} maxWidth="lg" open={openOpsecDialog.open}
onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}}
innerDialog={<TaskOpsecDialog task_id={task.id} view={openOpsecDialog.view} onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}} />}
/>) : null
/>
}
{openStdoutStderrDialog ?
(<MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
{openStdoutStderrDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
onClose={()=>{setOpenStdoutStderrDialog(false);}}
innerDialog={<TaskViewStdoutStderrDialog task_id={task.id} onClose={()=>{setOpenStdoutStderrDialog(false);}} />}
/>) : null
/>
}
{openEventingDialog &&
<MythicDialog
@@ -562,41 +557,36 @@ const SpeedDialDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput
return (
<React.Fragment>
<Backdrop open={openSpeedDial} onClick={()=>{setOpenSpeedDial(false);}} style={{zIndex: 2, position: "absolute"}}/>
{openTaskTagDialog ?
(<MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
{openTaskTagDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openTaskTagDialog}
onClose={()=>{setOpenTaskTagDialog(false);}}
innerDialog={<ViewEditTagsDialog me={me} target_object={"task_id"} target_object_id={task.id} onClose={()=>{setOpenTaskTagDialog(false);}} />}
/>) : null}
{openCommentDialog ?
(<MythicDialog fullWidth={true} maxWidth="lg" open={openCommentDialog}
/>
}
{openCommentDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openCommentDialog}
onClose={()=>{setOpenCommentDialog(false);}}
innerDialog={<TaskCommentDialog task_id={task.id} onClose={()=>{setOpenCommentDialog(false);}} />}
/>) : null
/>
}
{openParametersDialog ?
(<MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
{openParametersDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openParametersDialog}
onClose={()=>{setOpenParametersDialog(false);}}
innerDialog={<TaskViewParametersDialog task_id={task.id} onClose={()=>{setOpenParametersDialog(false);}} />}
/>) : null
/>
}
{openTokenDialog ?
(<MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
{openTokenDialog && <MythicDialog fullWidth={true} maxWidth="md" open={openTokenDialog}
onClose={()=>{setOpenTokenDialog(false);}}
innerDialog={<TaskTokenDialog token_id={task.token === undefined ? 0 : task.token.id} onClose={()=>{setOpenTokenDialog(false);}} />}
/>) : null
/>
}
{openOpsecDialog.open ?
(<MythicDialog fullWidth={true} maxWidth="md" open={openOpsecDialog.open}
{openOpsecDialog.open && <MythicDialog fullWidth={true} maxWidth="lg" open={openOpsecDialog.open}
onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}}
innerDialog={<TaskOpsecDialog task_id={task.id} view={openOpsecDialog.view} onClose={()=>{setOpenOpsecDialog({...openOpsecDialog, open: false});}} />}
/>) : null
/>
}
{openStdoutStderrDialog ?
(<MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
{openStdoutStderrDialog && <MythicDialog fullWidth={true} maxWidth="lg" open={openStdoutStderrDialog}
onClose={()=>{setOpenStdoutStderrDialog(false);}}
innerDialog={<TaskViewStdoutStderrDialog task_id={task.id} onClose={()=>{setOpenStdoutStderrDialog(false);}} />}
/>) : null
/>
}
{openEventingDialog &&
<MythicDialog
@@ -21,6 +21,7 @@ import {Backdrop, CircularProgress} from '@mui/material';
import Divider from '@mui/material/Divider';
import {b64DecodeUnicode} from './ResponseDisplay';
import {snackActions} from "../../utilities/Snackbar";
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
//if we need to get all the loaded commands for the callback and filter, use this
const GetLoadedCommandsQuery = gql`
@@ -977,7 +978,7 @@ export function TaskParametersDialog(props) {
}
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">{commandInfo.cmd}'s Parameters</DialogTitle>
<MythicDraggableDialogTitle>{commandInfo.cmd}'s Parameters</MythicDraggableDialogTitle>
<DialogContent dividers={true}>
<Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute"}}>
<CircularProgress color="inherit" />
@@ -10,7 +10,7 @@ import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
export const allTokenDataFragment = gql`
fragment allTokenData on token {
@@ -89,7 +89,7 @@ export function TaskTokenDialog(props) {
});
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">Token Information</DialogTitle>
<MythicDraggableDialogTitle >Token Information</MythicDraggableDialogTitle>
<TableContainer className="mythicElement">
<Table size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "scroll"}}>
<TableHead>
@@ -68,13 +68,14 @@ export function TaskViewParametersDialog(props) {
return <div>Error!</div>;
}
return (
<React.Fragment>
<MythicModifyStringDialog title={`View Task Parameters And Timestamps`}
onClose={props.onClose}
maxRows={40}
wrap={true}
value={comment} />
</React.Fragment>
<>
<MythicModifyStringDialog title={`View Task Parameters And Timestamps`}
onClose={props.onClose}
maxRows={40}
wrap={true}
value={comment} />
</>
);
}
@@ -19,6 +19,7 @@ import {TextField} from '@mui/material';
import {snackActions} from "../../utilities/Snackbar";
import {MeContext} from "../../App";
import {EventGroupTable} from "./EventGroupTable";
import {MythicDraggableDialogTitle} from "../../MythicComponents/MythicDraggableDialogTitle";
const triggerManualMutation = gql(`
mutation triggerManualMutation($eventgroup_id: Int!, $env_data: jsonb){
@@ -203,7 +204,7 @@ export function EventTriggerContextSelectDialog({onClose, triggerContext}) {
}
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">Trigger a workflow</DialogTitle>
<MythicDraggableDialogTitle >Trigger a workflow</MythicDraggableDialogTitle>
<DialogContent dividers={true} style={{maxHeight: "calc(70vh)"}}>
<DialogContentText>
Trigger a workflow with selected context and optional additional data.
+1 -1
View File
@@ -15,7 +15,7 @@ import {jwtDecode} from 'jwt-decode';
import {meState} from './cache';
import {getSkewedNow} from "./components/utilities/Time";
export const mythicUIVersion = "0.3.103";
export const mythicUIVersion = "0.3.104";
let fetchingNewToken = false;
@@ -1,17 +1,17 @@
{
"files": {
"main.css": "/new/static/css/main.262ce320.css",
"main.js": "/new/static/js/main.43cac978.js",
"main.js": "/new/static/js/main.97297187.js",
"sql-wasm.wasm": "/new/sql-wasm-7f26ab750fcdafabbe18c6525a66a0bd.wasm",
"static/media/mythic-red.png": "/new/static/media/mythic-red.203468a4e5240d239aa0.png",
"static/media/graphql.png": "/new/static/media/graphql.8f15978b39b0870a9f0e.png",
"static/media/Mythic_Logo.svg": "/new/static/media/Mythic_Logo.6842c911bebe36d6f83fc7ced4a2cd99.svg",
"index.html": "/new/index.html",
"main.262ce320.css.map": "/new/static/css/main.262ce320.css.map",
"main.43cac978.js.map": "/new/static/js/main.43cac978.js.map"
"main.97297187.js.map": "/new/static/js/main.97297187.js.map"
},
"entrypoints": [
"static/css/main.262ce320.css",
"static/js/main.43cac978.js"
"static/js/main.97297187.js"
]
}
+1 -1
View File
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.43cac978.js"></script><link href="/new/static/css/main.262ce320.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.97297187.js"></script><link href="/new/static/css/main.262ce320.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>