Skip to content

Commit

Permalink
few UI fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Dec 25, 2024
1 parent 801a2da commit 81a1955
Show file tree
Hide file tree
Showing 15 changed files with 120 additions and 260 deletions.
8 changes: 8 additions & 0 deletions MythicReactUI/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ 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.2.70] - 2024-12-25

### Changed

- Fixed an issue with wrong total counts for task output
- Fixed an issue with task tooltips being too long and not wrapping
- Re-ordered some of the buttons on the hamburger menu to bring more consistently used actions to the top

## [0.2.69]

### Changed
Expand Down
2 changes: 1 addition & 1 deletion MythicReactUI/src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export function App(props) {
<ThemeProvider theme={theme}>
<GlobalStyles theme={theme} />
<CssBaseline />
<Tooltip id={"my-tooltip"} style={{zIndex: 100000}}/>
<Tooltip id={"my-tooltip"} style={{zIndex: 100000, wordBreak: "break-word", width: "80%"}}/>
<ToastContainer limit={2} autoClose={3000}
theme={themeMode}
style={{maxWidth: "100%", minWidth: "40%", width: "40%", marginTop: "20px", display: "flex", flexWrap: "wrap",
Expand Down
3 changes: 2 additions & 1 deletion MythicReactUI/src/components/EventFeedNotifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ const subscribe_payloads = gql`

export function EventFeedNotifications(props) {
const me = props.me;
const [fromNow, setFromNow] = React.useState(getSkewedNow().toISOString());
//const fromNow = React.useRef( );
const { loading, error, data } = useSubscription(subscribe_payloads, {
variables: {fromNow: (getSkewedNow()).toISOString()},
variables: {fromNow: fromNow },
fetchPolicy: "no-cache",
shouldResubscribe: true,
onError: (errorData) => {
Expand Down
146 changes: 74 additions & 72 deletions MythicReactUI/src/components/TopAppBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,88 +269,27 @@ export function TopAppBar(props) {
Home
</ListSubheader>
}>
<div style={{marginLeft: "15px"}}>
<Typography style={{marginLeft: "15px", fontSize: 12}}>
<b>Mythic Version:</b> v{serverVersion}<br/>
<b>UI Version:</b> v{mythicUIVersion}<br/>
</div>
</Typography>

<ListItem button component={Link} to='/new' key={"home"} onClick={handleDrawerClose}>
<ListItemIcon ><SpaceDashboardTwoToneIcon fontSize={"large"} className="mythicElement" /></ListItemIcon>
<ListItemText primary={"Dashboard / Home"} />
</ListItem>
</List>
<List
subheader={
<ListSubheader className={classes.listSubHeader} component="div" id="nested-list-subheader">
Global Configurations
</ListSubheader>
}>
<ListItem button onClick={handleToggleGlobal}>
<ListItemIcon><LayersTwoToneIcon fontSize={"large"} /></ListItemIcon>
<ListItemText>Services</ListItemText>
{openGlobal ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={openGlobal} unmountOnExit >
<List component="div" disablePadding style={{border: 0}}>
<ListItem button className={classes.nested} target="_blank" component={Link} to='/jupyter' key={"jupyter"} onClick={handleDrawerClose}>
<ListItemIcon><CodeIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Jupyter Notebooks"} />
</ListItem>
<ListItem button className={classes.nested} target="_blank" component={Link} to='/console' key={"console"} onClick={handleDrawerClose}>
<ListItemIcon><StorageIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"GraphQL Console"} />
</ListItem>
<ListItem button className={classes.nested} component={Link} to='/new/consuming_services' key={"consuming"} onClick={handleDrawerClose}>
<ListItemIcon><PublicIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Consuming Services"} />
</ListItem>
</List>
</Collapse>
<ListItem button onClick={handleToggleCreate}>
<ListItemIcon><PostAddIcon fontSize={"large"} /></ListItemIcon>
<ListItemText>Create</ListItemText>
{openCreate ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={openCreate} unmountOnExit>
<List component="div" disablePadding style={{border: 0}}>
<ListItem button className={classes.nested} component={Link} to='/new/createpayload' key={"createpayload"} onClick={handleDrawerClose} state={{from: 'TopAppBar'}}>
<ListItemIcon><FontAwesomeIcon size={"2x"} icon={faBiohazard} /></ListItemIcon>
<ListItemText primary={"Create Payload"} />
</ListItem>
<ListItem button className={classes.nested} component={Link} to='/new/createwrapper' key={"createwrapper"} onClick={handleDrawerClose}>
<ListItemIcon><PostAddIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Create Wrapper"} />
</ListItem>
</List>
</Collapse>
<ListItem button onClick={handleToggleOperations}>
<ListItemIcon><SupervisorAccountIcon fontSize={"large"} /></ListItemIcon>
<ListItemText>Operation Config</ListItemText>
{openOperations ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={openOperations} unmountOnExit>
<List component="div" disablePadding style={{border: 0}}>
<ListItem button className={classes.nested} component={Link} to='/new/payloadtypes' key={"payloadtypes"} onClick={handleDrawerClose}>
<ListItemIcon><HeadsetTwoToneIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Agents & C2"} />
</ListItem>
<ListItem button className={classes.nested} component={Link} to='/new/operations' key={"modifyoperations"} onClick={handleDrawerClose}>
<ListItemIcon><EditIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Modify Operations"} />
</ListItem>
<ListItem button className={classes.nested} component={Link} to='/new/browserscripts' key={"browserscripts"} onClick={handleDrawerClose}>
<ListItemIcon><CodeIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"BrowserScripts"} />
</ListItem>
</List>
</Collapse>
</List>

<List
subheader={
<ListSubheader className={classes.listSubHeader} component="div" id="nested-list-subheader">
Operational Views
</ListSubheader>
}>
<ListItem className={classes.listSubHeader} button component={Link} to='/new/callbacks' key={"callbacks"} onClick={handleDrawerClose}>
<ListItemIcon><PhoneCallbackIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Active Callbacks"} />
</ListItem>
<ListItem button className={classes.listSubHeader} component={Link} to='/new/payloads' key={"payloads"} onClick={handleDrawerClose}>
<ListItemIcon><FontAwesomeIcon icon={faBiohazard} size="2x"/></ListItemIcon>
<ListItemText primary={"Payloads"} />
Expand Down Expand Up @@ -425,11 +364,74 @@ export function TopAppBar(props) {
<ListItemIcon><PlayCircleFilledTwoToneIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Eventing"} />
</ListItem>
<ListItem className={classes.listSubHeader} button component={Link} to='/new/callbacks' key={"callbacks"} onClick={handleDrawerClose}>
<ListItemIcon><PhoneCallbackIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Active Callbacks"} />
</ListItem>

</List>
<List
subheader={
<ListSubheader className={classes.listSubHeader} component="div" id="nested-list-subheader">
Global Configurations
</ListSubheader>
}>
<ListItem button onClick={handleToggleGlobal}>
<ListItemIcon><LayersTwoToneIcon fontSize={"large"} /></ListItemIcon>
<ListItemText>Services</ListItemText>
{openGlobal ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={openGlobal} unmountOnExit >
<List component="div" disablePadding style={{border: 0}}>
<ListItem button className={classes.nested} target="_blank" component={Link} to='/jupyter' key={"jupyter"} onClick={handleDrawerClose}>
<ListItemIcon><CodeIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Jupyter Notebooks"} />
</ListItem>
<ListItem button className={classes.nested} target="_blank" component={Link} to='/console' key={"console"} onClick={handleDrawerClose}>
<ListItemIcon><StorageIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"GraphQL Console"} />
</ListItem>
<ListItem button className={classes.nested} component={Link} to='/new/consuming_services' key={"consuming"} onClick={handleDrawerClose}>
<ListItemIcon><PublicIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Consuming Services"} />
</ListItem>
</List>
</Collapse>
<ListItem button onClick={handleToggleCreate}>
<ListItemIcon><PostAddIcon fontSize={"large"} /></ListItemIcon>
<ListItemText>Create</ListItemText>
{openCreate ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={openCreate} unmountOnExit>
<List component="div" disablePadding style={{border: 0}}>
<ListItem button className={classes.nested} component={Link} to='/new/createpayload' key={"createpayload"} onClick={handleDrawerClose} state={{from: 'TopAppBar'}}>
<ListItemIcon><FontAwesomeIcon size={"2x"} icon={faBiohazard} /></ListItemIcon>
<ListItemText primary={"Create Payload"} />
</ListItem>
<ListItem button className={classes.nested} component={Link} to='/new/createwrapper' key={"createwrapper"} onClick={handleDrawerClose}>
<ListItemIcon><PostAddIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Create Wrapper"} />
</ListItem>
</List>
</Collapse>
<ListItem button onClick={handleToggleOperations}>
<ListItemIcon><SupervisorAccountIcon fontSize={"large"} /></ListItemIcon>
<ListItemText>Operation Config</ListItemText>
{openOperations ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={openOperations} unmountOnExit>
<List component="div" disablePadding style={{border: 0}}>
<ListItem button className={classes.nested} component={Link} to='/new/payloadtypes' key={"payloadtypes"} onClick={handleDrawerClose}>
<ListItemIcon><HeadsetTwoToneIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Agents & C2"} />
</ListItem>
<ListItem button className={classes.nested} component={Link} to='/new/operations' key={"modifyoperations"} onClick={handleDrawerClose}>
<ListItemIcon><EditIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"Modify Operations"} />
</ListItem>
<ListItem button className={classes.nested} component={Link} to='/new/browserscripts' key={"browserscripts"} onClick={handleDrawerClose}>
<ListItemIcon><CodeIcon fontSize={"large"} className="mythicElement"/></ListItemIcon>
<ListItemText primary={"BrowserScripts"} />
</ListItem>
</List>
</Collapse>
</List>
<Divider />
</StyledDrawer>
{me?.user?.current_operation_banner_text !== "" &&
Expand Down
31 changes: 24 additions & 7 deletions MythicReactUI/src/components/pages/Callbacks/ResponseDisplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,20 @@ export const ResponseDisplay = (props) =>{
const NonInteractiveResponseDisplay = (props) => {
const [output, setOutput] = React.useState("");
const [rawResponses, setRawResponses] = React.useState([]);
const [taskID, setTaskID] = React.useState(props.task.id);
const seenResponseIDs = React.useRef([]);
const search = React.useRef("");
const [totalCount, setTotalCount] = React.useState(0);
const [openBackdrop, setOpenBackdrop] = React.useState(true);
const togglingAllOutputToPaginated = React.useRef(false);
const initialResponseStreamLimit = GetMythicSetting({setting_name: "experiment-responseStreamLimit", default_value: 50});
const [fetchMoreResponses] = useLazyQuery(getResponsesLazyQuery, {
fetchPolicy: "network-only",
onCompleted: (data) => {
data.response.forEach( (r) => {
if(!seenResponseIDs.current.includes(r.id)){
seenResponseIDs.current.push(r.id);
}
})
// set raw responses to be what we just manually fetched
const responseArray = data.response.map( r =>{ return {...r, response: b64DecodeUnicode(r.response)}});
setRawResponses(responseArray);
Expand Down Expand Up @@ -144,6 +150,7 @@ const NonInteractiveResponseDisplay = (props) => {

setTotalCount(1);
setOpenBackdrop(false);
togglingAllOutputToPaginated.current = true;
},
onError: (data) => {

Expand All @@ -159,39 +166,49 @@ const NonInteractiveResponseDisplay = (props) => {
}else{
fetchAllResponses({variables: {task_id: props.task.id, search: "%" + search.current + "%"}})
}
}else{
}else if(togglingAllOutputToPaginated.current){
// going from select all output to not select all output
// don't fetch this on first load
onSubmitPageChange(1);
togglingAllOutputToPaginated.current = false;
}
//}
}, [props.selectAllOutput]);
}, [props.selectAllOutput, togglingAllOutputToPaginated.current]);
React.useEffect( () => {
setOpenBackdrop(true);
setOutput("");
setRawResponses([]);
setTotalCount(0);
onSubmitPageChange(1);
//onSubmitPageChange(1);
}, [props.task.id]);
const subscriptionDataCallback = ({data}) => {
//console.log("fetchLimit", fetchLimit, "totalCount", totalCount);

if(rawResponses.length >= initialResponseStreamLimit && initialResponseStreamLimit > 0 && !props.selectAllOutput){
// we won't display it
console.log("got more than we can see currently", totalCount);
setOpenBackdrop(false);
setTotalCount(totalCount + data.data.response_stream.length);
let newTotal = totalCount;
data.data.response_stream.forEach( (r) => {
if(!seenResponseIDs.current.includes(r.id)){
newTotal += 1;
seenResponseIDs.current.push(r.id);
}
})
setTotalCount(newTotal);
return;
}
// we still have some room to view more, but only room for initialResponseStreamLimit - totalFetched.current
let newTotal = totalCount;
const newerResponses = data.data.response_stream.reduce( (prev, cur) => {
if(!seenResponseIDs.current.includes(cur.id)){
newTotal += 1;
seenResponseIDs.current.push(cur.id);
}
let prevIndex = prev.findIndex( (v,i,a) => v.id === cur.id);
if(prevIndex >= 0){
prev[prevIndex] = {...cur, response: b64DecodeUnicode(cur.response)};
return prev;
}
newTotal += 1;
return [...prev, {...cur, response: b64DecodeUnicode(cur.response)}]
}, rawResponses);
// sort them to make sure we're still in order
Expand Down
4 changes: 2 additions & 2 deletions MythicReactUI/src/components/pages/Login/LoginForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import {Button, Paper} from '@mui/material';
import MythicTextField from '../../MythicComponents/MythicTextField';
import logo from '../../../assets/mythic-red.png';
import { Navigate } from 'react-router-dom';
import {meState, successfulLogin, FailedRefresh, mePreferences} from '../../../cache';
import {meState, successfulLogin, FailedRefresh} from '../../../cache';
import { useReactiveVar } from '@apollo/client';
import {restartWebsockets, isJWTValid} from '../../../index';
import {isJWTValid} from '../../../index';
import { snackActions } from '../../utilities/Snackbar';
import CardContent from '@mui/material/CardContent';
import Grow from '@mui/material/Grow';
Expand Down
2 changes: 1 addition & 1 deletion MythicReactUI/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import jwt_decode from 'jwt-decode';
import {meState} from './cache';
import {getSkewedNow} from "./components/utilities/Time";

export const mythicUIVersion = "0.2.69";
export const mythicUIVersion = "0.2.70";

let fetchingNewToken = false;

Expand Down
6 changes: 3 additions & 3 deletions mythic-react-docker/mythic/public/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"files": {
"main.css": "/new/static/css/main.602591e6.css",
"main.js": "/new/static/js/main.909f2e9d.js",
"main.js": "/new/static/js/main.8efe2dbc.js",
"static/media/mythic-red.png": "/new/static/media/mythic-red.203468a4e5240d239aa0.png",
"static/media/mythic_red_small.svg": "/new/static/media/mythic_red_small.793b41cc7135cdede246661ec232976b.svg",
"index.html": "/new/index.html",
"main.602591e6.css.map": "/new/static/css/main.602591e6.css.map",
"main.909f2e9d.js.map": "/new/static/js/main.909f2e9d.js.map"
"main.8efe2dbc.js.map": "/new/static/js/main.8efe2dbc.js.map"
},
"entrypoints": [
"static/css/main.602591e6.css",
"static/js/main.909f2e9d.js"
"static/js/main.8efe2dbc.js"
]
}
2 changes: 1 addition & 1 deletion mythic-react-docker/mythic/public/index.html
Original file line number Diff line number Diff line change
@@ -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.909f2e9d.js"></script><link href="/new/static/css/main.602591e6.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.8efe2dbc.js"></script><link href="/new/static/css/main.602591e6.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

Large diffs are not rendered by default.

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions mythic-react-docker/mythic/public/static/js/main.909f2e9d.js

This file was deleted.

This file was deleted.

Loading

0 comments on commit 81a1955

Please sign in to comment.