Skip to content

Commit

Permalink
File browser fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Aug 12, 2024
1 parent 69e714c commit 96c61c7
Show file tree
Hide file tree
Showing 21 changed files with 150 additions and 69 deletions.
11 changes: 11 additions & 0 deletions MythicReactUI/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ 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.21] - 2024-08-12

### Changed

- Fixed an issue with file browser context menu available through modals
- Added highlighting for .sh files
- Fixed an issue where auto-tasking wouldn't fire for paths that didn't already exist
- Updated dark mode file browser selection colors to be easier to see with info icons
- Updated file browser to show errored task for failed paths to make it easier to see errors
- Added a toast notification on file browser task errors

## [0.2.20] - 2024-08-11

### Changed
Expand Down
4 changes: 2 additions & 2 deletions MythicReactUI/src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export function App(props) {
successOnMain: '#1ae302',
errorOnMain: '#ff656b',
infoOnMain: '#67ceff',
selectedCallbackColor: themeMode === 'dark' ? '#436b9f' : '#c6e5f6',
selectedCallbackColor: themeMode === 'dark' ? '#26456e' : '#c6e5f6',
selectedCallbackHierarchyColor: themeMode === 'dark' ? '#273e5d' : '#deeff8',
materialReactTableHeader: themeMode === 'dark' ? '#484848' : '#d5d5d5',
tableBorder: themeMode === 'dark' ? 'rgba(81,81,81,1)' : 'rgba(224,224,224,1)',
Expand Down Expand Up @@ -164,7 +164,7 @@ export function App(props) {
onClose={()=>{setOpenRefreshDialog(false);}} />}
/>
}
<div style={{ margin: '0px 0px 0px 0px', flexGrow: 1, flexDirection: 'column', height: "calc(100% - 4rem)", }}>
<div style={{ margin: '0px 2px 0px 5px', flexGrow: 1, flexDirection: 'column', height: "calc(100% - 4rem)", }}>
<Routes>
<Route path='/new/login' element={<LoginForm me={me}/>}/>
<Route path='/new/invite' element={<InviteForm me={me}/>}/>
Expand Down
5 changes: 5 additions & 0 deletions MythicReactUI/src/components/MythicComponents/MythicDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export function MythicDialog(props) {
props.onClose();
}
}
const dialogOnContextMenu = (e) => {
e.stopPropagation();

}
return (
<Dialog
open={props.open}
Expand All @@ -48,6 +52,7 @@ export function MythicDialog(props) {
aria-labelledby="scroll-dialog-title"
aria-describedby="scroll-dialog-description"
onClick={dialogOnClick}
onContextMenu={dialogOnContextMenu}
>
{props.innerDialog}
</Dialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ const FileBrowserVirtualTreePreMemo = ({
selectedFolderData,
tabInfo,
}) => {
const gridRef = React.useRef(null);
const flattenNode = useCallback(
// node is just a full_path_text
(node, group, host, depth = 0) => {
Expand Down Expand Up @@ -412,6 +413,14 @@ const FileBrowserVirtualTreePreMemo = ({
return finalData;
//nodes.map((node) => flattenNode(node)).flat()
},[flattenNode, treeRootData, treeAdjMatrix, showDeletedFiles]);
React.useEffect( () => {
let rowIndex = flattenedNodes?.findIndex(e => e.full_path_text === selectedFolderData.full_path_text);
if(rowIndex >= 0){
if(gridRef.current){
gridRef.current?.scrollToItem(rowIndex, "start")
}
}
}, [selectedFolderData.full_path_text, flattenedNodes]);
return flattenedNodes.length > 0 ? (
<StyledAutoSizer>
{(AutoSizerProps) => (
Expand All @@ -423,6 +432,7 @@ const FileBrowserVirtualTreePreMemo = ({
itemCount={flattenedNodes.length}
itemKey={itemKey}
itemSize={24}
ref={gridRef}
>
{(ListProps) => (
<VirtualTreeRow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {b64DecodeUnicode} from "./ResponseDisplay";
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import PlaylistRemoveIcon from '@mui/icons-material/PlaylistRemove';
import {SetMythicSetting, useMythicSetting} from "../../MythicComponents/MythicSavedUserSetting";
import {RenderSingleTask} from "../SingleTaskView/SingleTaskView";

const fileDataFragment = gql`
fragment fileObjData on mythictree {
Expand Down Expand Up @@ -105,7 +106,19 @@ const fileDataSubscription = gql`
}
}
`;

const fileBrowserTaskSub = gql`
subscription fileBrowserTasks($now: timestamp!, $callback_id: Int!){
task_stream(
batch_size: 10,
cursor: {initial_value: {timestamp: $now}},
where: {tasking_location: {_eq: "file_browser"}, callback_id: {_eq: $callback_id}}
){
id
status
completed
}
}
`;
export const getAllParentNodes = (node) => {
let linuxPathSeparatorIndex = node.full_path_text.indexOf("/");
let windowsPathSeparatorIndex = node.full_path_text.indexOf("\\");
Expand Down Expand Up @@ -199,7 +212,7 @@ export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me }) =>
path: "",
token: 0,
});
const autoTaskLsOnEmptyDirectories = React.useRef(false);
const autoTaskLsOnEmptyDirectoriesRef = React.useRef(false);
const taskingData = React.useRef({"parameters": "", "ui_feature": "file_browser:list"});
const mountedRef = React.useRef(true);
const tableOpenedPathIdRef = React.useRef({
Expand Down Expand Up @@ -328,6 +341,18 @@ export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me }) =>

}
})
useSubscription(fileBrowserTaskSub, {
variables: {now: fromNow.current, callback_id: tabInfo.callbackID},
fetchPolicy: "no-cache",
onData: ({data}) => {
for(let i = 0; i < data.data.task_stream.length; i++){
if(data.data.task_stream[i].status.toLowerCase().includes("error") && data.data.task_stream[i].completed){
snackActions.warning(<RenderSingleTask task_id={data.data.task_stream[i].id} />,
{toastId: data.data.task_stream[i].id, autoClose: false, closeOnClick: false});
}
}
}
})
const [getFolderData] = useLazyQuery(folderQuery, {
onError: (data) => {
console.error(data);
Expand Down Expand Up @@ -398,7 +423,26 @@ export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me }) =>
setTreeAdjMtx(newMatrix);
//console.log("just set treeAdjMtx, about to close backdrop")
setBackdropOpen(false);
setSelectedFolderData({...selectedFolderData, finished: true});
if(data.self.length > 0){
setSelectedFolderData({...selectedFolderData, task_id: data.self[0].task_id, success: data.self[0].success});
// this path exists, let's see if we have data for it and if the user wants us to auto-issue an ls for the path
let newAllData = Object.keys(newMatrix[selectedFolderData.group]?.[selectedFolderData.host]?.[data.self[0].full_path_text] || {});
if(autoTaskLsOnEmptyDirectoriesRef.current){
if(newAllData.length === 0 && data.self[0].full_path_text !== "" && data.self[0].success === null){
onListFilesButtonFromTableWithNoEntries()
}
}
if(data.self[0].success === false){
snackActions.warning("Failed to list out path: " + data.self[0].full_path_text)
}
} else {
// we couldn't find the path specified, so we must not have data for it, so check if the user wants us to auto issue an ls
if(autoTaskLsOnEmptyDirectoriesRef.current){
onListFilesButtonFromTableWithNoEntries()
}
}


},
});
const onSetTableData = useCallback((nodeData) => {
Expand Down Expand Up @@ -526,7 +570,7 @@ export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me }) =>
variables: {parent_path_text: path, parents: parentNodes}
})
setBackdropOpen(true);
setSelectedFolderData({host, group, full_path_text: path, id: 0});
setSelectedFolderData({host, group, full_path_text: path, id: 0, success: null});
tableOpenedPathIdRef.current = {
group: group,
host: host,
Expand Down Expand Up @@ -559,7 +603,7 @@ export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me }) =>
<FileBrowserTableTop
tabInfo={tabInfo}
taskingTableTopTypedDataRef={taskingTableTopTypedDataRef}
autoTaskLsOnEmptyDirectoriesRef={autoTaskLsOnEmptyDirectories}
autoTaskLsOnEmptyDirectoriesRef={autoTaskLsOnEmptyDirectoriesRef}
onChangeSelectedToken={onChangeSelectedToken}
selectedFolderData={selectedFolderData}
onListFilesButton={onListFilesButton}
Expand All @@ -582,7 +626,7 @@ export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me }) =>
treeAdjMatrix={treeAdjMtx}
onListFilesButtonFromTableWithNoEntries={onListFilesButtonFromTableWithNoEntries}
selectedFolderData={selectedFolderData}
autoTaskLsOnEmptyDirectories={autoTaskLsOnEmptyDirectories.current}
autoTaskLsOnEmptyDirectories={autoTaskLsOnEmptyDirectoriesRef.current}
onTaskRowAction={onTaskRowAction}
onTaskRowActions={onTaskRowActions}
me={me}
Expand Down Expand Up @@ -731,6 +775,11 @@ const FileBrowserTableTop = ({
const onToggleAutoTaskLsOnEmptyDirectories = () => {
SetMythicSetting({setting_name: "autoTaskLsOnEmptyDirectories", value: !autoTaskLsOnEmptyDirectories});
setAutoTaskLsOnEmptyDirectories(!autoTaskLsOnEmptyDirectories);
if(autoTaskLsOnEmptyDirectories){
snackActions.info("No longer auto issuing listings for empty paths");
} else {
snackActions.success("Now starting to auto issue listings for empty paths");
}
}
const goToDirectory = () => {
if(fullPath === ""){return}
Expand Down Expand Up @@ -769,7 +818,7 @@ const FileBrowserTableTop = ({
<React.Fragment>
<MythicStyledTooltip title={`Task current callback (${tabInfo["displayID"]}) to list contents`}>
<IconButton style={{ padding: '0 0px 0 0 ' }}
disableRipple={true} disableFocusRipple={true}

onClick={onLocalListFilesButton}
size="large">
<RefreshIcon color='info' />
Expand All @@ -778,7 +827,6 @@ const FileBrowserTableTop = ({
<MythicStyledTooltip title={`Upload file to folder via current callback (${tabInfo["displayID"]})`}>
<IconButton style={{ padding: '3px' }}
onClick={onLocalUploadFileButton}
disableRipple={true} disableFocusRipple={true}
size="large">
<CloudUploadIcon color="info" />
</IconButton>
Expand All @@ -787,7 +835,6 @@ const FileBrowserTableTop = ({
<MythicStyledTooltip title={"Currently tasking listing on empty directories, click to toggle off"} >
<IconButton style={{padding: "3px"}}
onClick={onToggleAutoTaskLsOnEmptyDirectories}
disableRipple={true} disableFocusRipple={true}
size={"large"}>
<PlaylistAddIcon color={"success"} ></PlaylistAddIcon>
</IconButton>
Expand All @@ -796,7 +843,6 @@ const FileBrowserTableTop = ({
<MythicStyledTooltip title={"Currently not tasking listing on empty directories, click to toggle on"} >
<IconButton style={{padding: "3px"}}
onClick={onToggleAutoTaskLsOnEmptyDirectories}
disableRipple={true} disableFocusRipple={true}
size={"large"}>
<PlaylistRemoveIcon color={"secondary"} ></PlaylistRemoveIcon>
</IconButton>
Expand All @@ -806,7 +852,6 @@ const FileBrowserTableTop = ({
<IconButton
style={{ padding: '3px' }}
onClick={onLocalToggleShowDeletedFiles}
disableRipple={true} disableFocusRipple={true}
size="large">
{showDeletedFiles ? (
<VisibilityIcon color="success" />
Expand All @@ -829,7 +874,6 @@ const FileBrowserTableTop = ({
<MythicStyledTooltip title={`Move back to previous listing`}>
<IconButton style={{ padding: '3px' }}
disabled={historyIndex >= history.length -1 }
disableRipple={true} disableFocusRipple={true}
onClick={moveIndexToPreviousListing}
color='info'
size="large">
Expand All @@ -839,7 +883,6 @@ const FileBrowserTableTop = ({
<MythicStyledTooltip title={`Move to next listing`}>
<IconButton style={{ padding: '3px' }}
disabled={historyIndex <= 0}
disableRipple={true} disableFocusRipple={true}
onClick={moveIndexToNextListing}
size="large"
color='info'>
Expand All @@ -849,7 +892,6 @@ const FileBrowserTableTop = ({
<MythicStyledTooltip title={"Move up a directory"} >
<IconButton style={{padding: "0 0 0 0"}}
onClick={onLocalMoveUpDirectoryButton}
disableRipple={true} disableFocusRipple={true}
disabled={selectedFolderData?.parent_path_text?.length === 0 || selectedFolderData.root || fullPath === ""}
>
<KeyboardReturnIcon style={{rotate: "90deg"}} ></KeyboardReturnIcon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {faPhotoVideo} from '@fortawesome/free-solid-svg-icons';
import {PreviewFileMediaDialog} from "../Search/PreviewFileMedia";
import RefreshIcon from '@mui/icons-material/Refresh';
import {Dropdown, DropdownMenuItem, DropdownNestedMenuItem} from "../../MythicComponents/MythicNestedMenus";
import {RenderSingleTask} from "../SingleTaskView/SingleTaskView";

const getFileDownloadHistory = gql`
query getFileDownloadHistory($full_path_text: String!, $host: String!, $group: [String!]) {
Expand Down Expand Up @@ -195,10 +196,14 @@ export const CallbacksTabsFileBrowserTable = (props) => {
return "normal";
} else if(sortedData.length === 0 && props?.selectedFolderData?.metadata?.has_children && props?.selectedFolderData?.success === null){
return "fetchLocal";
}else if(sortedData.length === 0 && !props?.selectedFolderData?.metadata?.has_children && props?.selectedFolderData?.success === null){
}else if(sortedData.length === 0 && !props?.selectedFolderData?.metadata?.has_children && props?.selectedFolderData?.success === null) {
return "fetchRemote";
}else if(sortedData.length === 0 && props?.selectedFolderData?.success === false){
return "showTask";
} else if(sortedData.length === 0){
return "fetchRemote";
} else {
return "normal";
}
}
const displayFormat = getDisplayFormat();
Expand All @@ -213,21 +218,6 @@ export const CallbacksTabsFileBrowserTable = (props) => {
setAllData(newAllData);
//console.log("just set all data")
}, [props.selectedFolderData, props.treeAdjMatrix]);
useEffect(() => {
// when the folder changes, we need to aggregate all of the entries
//console.log(props.selectedFolderData, props.treeAdjMatrix, props.treeRootData)
let desiredPath = props.selectedFolderData.full_path_text;
if(props.selectedFolderData.id === props.selectedFolderData.host){
desiredPath = "";
}
let newAllData = Object.keys(props.treeAdjMatrix[props.selectedFolderData.group]?.[props.selectedFolderData.host]?.[desiredPath] || {});
if(props.autoTaskLsOnEmptyDirectories){
if(newAllData.length === 0 && desiredPath !== "" && props.selectedFolderData.finished && props.selectedFolderData.success === null){
props.onListFilesButtonFromTableWithNoEntries()
}
}
//console.log("just set all data")
}, [props.selectedFolderData]);
const onRowDoubleClick = (e, rowIndex, rowData) => {
if (!rowData.can_have_children) {
return;
Expand Down Expand Up @@ -544,8 +534,9 @@ export const CallbacksTabsFileBrowserTable = (props) => {
</div>
</div>
}


{displayFormat === "showTask" && props.selectedFolderData?.task_id > 0 &&
<RenderSingleTask task_id={props.selectedFolderData?.task_id} />
}
{openContextMenu &&
<MythicDialog fullWidth={true} maxWidth="xs" open={openContextMenu}
onClose={() => {
Expand Down Expand Up @@ -731,6 +722,7 @@ const FileBrowserTableRowActionCell = ({ rowData, cellData, onTaskRowAction, tre
};
const handleDropdownToggle = (evt) => {
evt.stopPropagation();
evt.preventDefault();
setDropdownOpen((prevOpen) => !prevOpen);
};
const handleMenuItemClick = (event, click) => {
Expand Down Expand Up @@ -835,6 +827,11 @@ const FileBrowserTableRowActionCell = ({ rowData, cellData, onTaskRowAction, tre
}
return options;
}
const openFilePreview = (event) => {
event.stopPropagation();
event.preventDefault();
setOpenPreviewMediaDialog(true);
}
return (
<React.Fragment>
<IconButton
Expand All @@ -852,7 +849,7 @@ const FileBrowserTableRowActionCell = ({ rowData, cellData, onTaskRowAction, tre
{treeRootData[selectedFolderData.host][cellData]?.filemeta.length > 0 ?
<MythicStyledTooltip title={"Preview Media"}>
<FontAwesomeIcon icon={faPhotoVideo} style={{height: "15px", marginRight: "5px", position: "relative", cursor: "pointer", display: "inline-block"}}
onClick={() => setOpenPreviewMediaDialog(true)}/>
onClick={openFilePreview}/>
</MythicStyledTooltip>

: null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {hideCallbacksMutation} from './CallbackMutations';
import {snackActions} from "../../utilities/Snackbar";
import {CallbacksTableLastCheckinCell, CallbacksTablePayloadTypeCell, CallbacksTableIPCell} from "./CallbacksTableRow";
import { DataGrid } from '@mui/x-data-grid';
import {useTheme} from '@mui/material/styles';


const callbacksAndFeaturesQuery = gql`
Expand Down
Loading

0 comments on commit 96c61c7

Please sign in to comment.