mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
updated table filtering
This commit is contained in:
+180
@@ -0,0 +1,180 @@
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
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 FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import ToggleButton from '@mui/material/ToggleButton';
|
||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
const DEFAULT_FILTER = {
|
||||
include: [],
|
||||
exclude: [],
|
||||
includeMode: "any",
|
||||
caseSensitive: false,
|
||||
};
|
||||
|
||||
const splitFilterTerms = (value) => {
|
||||
if(Array.isArray(value)){
|
||||
return value.map((entry) => String(entry).trim()).filter(Boolean);
|
||||
}
|
||||
if(value === undefined || value === null){
|
||||
return [];
|
||||
}
|
||||
return String(value)
|
||||
.split(/[\n,]+/)
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
export const normalizeGridColumnFilter = (value) => {
|
||||
if(value === undefined || value === null){
|
||||
return {...DEFAULT_FILTER};
|
||||
}
|
||||
if(typeof value === "string" || typeof value === "number" || typeof value === "boolean"){
|
||||
return {
|
||||
...DEFAULT_FILTER,
|
||||
include: splitFilterTerms(value),
|
||||
};
|
||||
}
|
||||
if(typeof value === "object"){
|
||||
return {
|
||||
include: splitFilterTerms(value.include),
|
||||
exclude: splitFilterTerms(value.exclude),
|
||||
includeMode: value.includeMode === "all" ? "all" : "any",
|
||||
caseSensitive: Boolean(value.caseSensitive),
|
||||
};
|
||||
}
|
||||
return {...DEFAULT_FILTER};
|
||||
};
|
||||
|
||||
export const isGridColumnFilterActive = (value) => {
|
||||
const normalizedFilter = normalizeGridColumnFilter(value);
|
||||
return normalizedFilter.include.length > 0 || normalizedFilter.exclude.length > 0;
|
||||
};
|
||||
|
||||
export const getUpdatedGridFilterOptions = (filterOptions, key, value) => {
|
||||
const nextFilterOptions = {...filterOptions};
|
||||
if(!key){
|
||||
return nextFilterOptions;
|
||||
}
|
||||
const normalizedFilter = normalizeGridColumnFilter(value);
|
||||
if(isGridColumnFilterActive(normalizedFilter)){
|
||||
nextFilterOptions[key] = normalizedFilter;
|
||||
}else{
|
||||
delete nextFilterOptions[key];
|
||||
}
|
||||
return nextFilterOptions;
|
||||
};
|
||||
|
||||
export const gridValuePassesFilter = (value, filterOption) => {
|
||||
const normalizedFilter = normalizeGridColumnFilter(filterOption);
|
||||
if(!isGridColumnFilterActive(normalizedFilter)){
|
||||
return true;
|
||||
}
|
||||
const cellText = String(value ?? "");
|
||||
const haystack = normalizedFilter.caseSensitive ? cellText : cellText.toLowerCase();
|
||||
const normalizeTerm = (term) => normalizedFilter.caseSensitive ? term : term.toLowerCase();
|
||||
const termMatches = (term) => haystack.includes(normalizeTerm(term));
|
||||
const includeMatches = normalizedFilter.include.length === 0 ? true :
|
||||
normalizedFilter.includeMode === "all" ?
|
||||
normalizedFilter.include.every(termMatches) :
|
||||
normalizedFilter.include.some(termMatches);
|
||||
if(!includeMatches){
|
||||
return false;
|
||||
}
|
||||
return !normalizedFilter.exclude.some(termMatches);
|
||||
};
|
||||
|
||||
export function GridColumnFilterDialog({filterValue, onClose, onSubmit, selectedColumn}) {
|
||||
const normalizedFilter = React.useMemo(() => normalizeGridColumnFilter(filterValue), [filterValue]);
|
||||
const [includeText, setIncludeText] = React.useState(normalizedFilter.include.join("\n"));
|
||||
const [excludeText, setExcludeText] = React.useState(normalizedFilter.exclude.join("\n"));
|
||||
const [includeMode, setIncludeMode] = React.useState(normalizedFilter.includeMode);
|
||||
const [caseSensitive, setCaseSensitive] = React.useState(normalizedFilter.caseSensitive);
|
||||
const columnName = selectedColumn?.name || selectedColumn?.plaintext || selectedColumn?.key || "Column";
|
||||
const buildFilter = () => ({
|
||||
include: splitFilterTerms(includeText),
|
||||
exclude: splitFilterTerms(excludeText),
|
||||
includeMode,
|
||||
caseSensitive,
|
||||
});
|
||||
const handleSubmit = () => {
|
||||
onSubmit(buildFilter());
|
||||
onClose();
|
||||
};
|
||||
const handleClear = () => {
|
||||
onSubmit({...DEFAULT_FILTER});
|
||||
onClose();
|
||||
};
|
||||
const onIncludeModeChange = (event, value) => {
|
||||
if(value !== null){
|
||||
setIncludeMode(value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="mythic-grid-filter-dialog">
|
||||
<DialogTitle className="mythic-grid-filter-dialog-title">
|
||||
Filter {columnName}
|
||||
</DialogTitle>
|
||||
<DialogContent className="mythic-grid-filter-dialog-content">
|
||||
<Typography className="mythic-grid-filter-dialog-copy" component="div">
|
||||
Add one term per line, or separate terms with commas. Include terms decide what can stay visible; exclude terms remove matching rows.
|
||||
</Typography>
|
||||
<Box className="mythic-grid-filter-dialog-mode-row">
|
||||
<Typography className="mythic-grid-filter-dialog-label" component="div">
|
||||
Include matching
|
||||
</Typography>
|
||||
<ToggleButtonGroup
|
||||
exclusive
|
||||
onChange={onIncludeModeChange}
|
||||
size="small"
|
||||
value={includeMode}
|
||||
>
|
||||
<ToggleButton value="any">Any</ToggleButton>
|
||||
<ToggleButton value="all">All</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
<div className="mythic-grid-filter-dialog-fields">
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Include rows matching"
|
||||
minRows={4}
|
||||
multiline
|
||||
onChange={(event) => setIncludeText(event.target.value)}
|
||||
placeholder={"admin\nprod"}
|
||||
value={includeText}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Exclude rows matching"
|
||||
minRows={4}
|
||||
multiline
|
||||
onChange={(event) => setExcludeText(event.target.value)}
|
||||
placeholder={"debug\ntest"}
|
||||
value={excludeText}
|
||||
/>
|
||||
</div>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={caseSensitive}
|
||||
onChange={(event) => setCaseSensitive(event.target.checked)}
|
||||
size="small"
|
||||
/>
|
||||
}
|
||||
label="Case sensitive"
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions className="mythic-grid-filter-dialog-actions">
|
||||
<Button onClick={handleClear}>Clear</Button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={handleSubmit} variant="contained">Apply Filter</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -96,6 +96,22 @@ const HeaderCell = ({
|
||||
clickOption({event, ...menuContext});
|
||||
setOpenContextMenu(false);
|
||||
};
|
||||
const filterMenuOption = React.useMemo(
|
||||
() => resolvedContextMenuOptions.find((option) => option.type === "item" && option.name === "Filter Column"),
|
||||
[resolvedContextMenuOptions]
|
||||
);
|
||||
const handleFilterIndicatorClick = useCallback(
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if(filterMenuOption?.disabled || !filterMenuOption?.click){
|
||||
return;
|
||||
}
|
||||
filterMenuOption.click({event, ...menuContext});
|
||||
setOpenContextMenu(false);
|
||||
},
|
||||
[filterMenuOption, menuContext]
|
||||
);
|
||||
|
||||
const handleClicks = useSingleAndDoubleClick(handleClick, handleDoubleClick);
|
||||
const className = `${classes.headerCell} ${item.disableSort ? classes.headerCellNoSort : ""}`;
|
||||
@@ -115,9 +131,15 @@ const HeaderCell = ({
|
||||
</Typography>
|
||||
<Box className={classes.headerActions}>
|
||||
{isFiltered &&
|
||||
<span className={`${classes.headerIndicator} ${classes.headerFilterIcon}`} title="Filtered">
|
||||
<button
|
||||
aria-label={`Edit filter for ${typeof headerLabel === "string" ? headerLabel : "column"}`}
|
||||
className={`${classes.headerIndicator} ${classes.headerFilterIcon}`}
|
||||
onClick={handleFilterIndicatorClick}
|
||||
title="Edit Filter"
|
||||
type="button"
|
||||
>
|
||||
<FilterAltOutlinedIcon fontSize="inherit" />
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
{isSorted &&
|
||||
<span className={`${classes.headerIndicator} ${classes.headerSortIcon}`} title={`Sorted ${sortDirection === 'ASC' ? 'ascending' : 'descending'}`}>
|
||||
|
||||
+17
-1
@@ -10,6 +10,7 @@ import {GetMythicSetting, useSetMythicSetting} from "../MythicSavedUserSetting";
|
||||
import FitScreenIcon from '@mui/icons-material/FitScreen';
|
||||
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
||||
import ViewColumnIcon from '@mui/icons-material/ViewColumn';
|
||||
import FilterAltOutlinedIcon from '@mui/icons-material/FilterAltOutlined';
|
||||
|
||||
const HeaderCellContext = createContext({});
|
||||
|
||||
@@ -18,6 +19,21 @@ const MIN_FLEX_COLUMN_WIDTH = 150;
|
||||
const AUTOSIZE_HORIZONTAL_PADDING = 44;
|
||||
const AUTOSIZE_HEADER_EXTRA_WIDTH = 28;
|
||||
const headerMenuIconStyle = {fontSize: "1rem", marginRight: "8px"};
|
||||
const normalizeHeaderContextMenuOption = (option) => {
|
||||
if(option?.name === "Filter Column"){
|
||||
return {
|
||||
...option,
|
||||
icon: <FilterAltOutlinedIcon style={headerMenuIconStyle} />,
|
||||
};
|
||||
}
|
||||
if(option?.menuItems){
|
||||
return {
|
||||
...option,
|
||||
menuItems: option.menuItems.map(normalizeHeaderContextMenuOption),
|
||||
};
|
||||
}
|
||||
return option;
|
||||
};
|
||||
|
||||
let autosizeCanvas;
|
||||
|
||||
@@ -506,7 +522,7 @@ const ResizableGridWrapper = ({
|
||||
click: () => resetColumnWidths(),
|
||||
},
|
||||
];
|
||||
return [...sizingOptions, ...(contextMenuOptions || [])];
|
||||
return [...sizingOptions, ...(contextMenuOptions || []).map(normalizeHeaderContextMenuOption)];
|
||||
}, [autosizeAllColumns, autosizeColumn, columns, contextMenuOptions, resetColumnWidths]);
|
||||
|
||||
useEffect( () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useMemo, useContext} from 'react';
|
||||
import {MythicDialog, MythicModifyStringDialog} from '../../MythicComponents/MythicDialog';
|
||||
import {MythicDialog} from '../../MythicComponents/MythicDialog';
|
||||
import {
|
||||
exportCallbackConfigQuery,
|
||||
hideCallbackMutation, lockCallbackMutation, unlockCallbackMutation, updateCallbackTriggerMutation,
|
||||
@@ -56,6 +56,12 @@ import {EventTriggerContextSelectDialog} from "../Eventing/EventTriggerContextSe
|
||||
import {useMythicLazyQuery} from "../../utilities/useMythicLazyQuery";
|
||||
import {faFilter} from '@fortawesome/free-solid-svg-icons';
|
||||
import {getReadableTextColor, isValidHexColor} from "../../MythicComponents/MythicColorInput";
|
||||
import {
|
||||
GridColumnFilterDialog,
|
||||
getUpdatedGridFilterOptions,
|
||||
gridValuePassesFilter,
|
||||
isGridColumnFilterActive
|
||||
} from "../../MythicComponents/MythicResizableGrid/GridColumnFilterDialog";
|
||||
|
||||
export const getCustomBrowsersQuery = gql(`
|
||||
query getCustomBrowsersQuery{
|
||||
@@ -749,7 +755,7 @@ function CallbacksTablePreMemo(props){
|
||||
() =>
|
||||
columnOrder.reduce( (prev, cur) => {
|
||||
if(columnVisibility.visible.includes(cur.name) || cur.name === "Interact"){
|
||||
if(filterOptions[cur.key] && String(filterOptions[cur.key]).length > 0){
|
||||
if(isGridColumnFilterActive(filterOptions[cur.key])){
|
||||
return [...prev, {...cur, filtered: true}];
|
||||
}else{
|
||||
return [...prev, {...cur}];
|
||||
@@ -820,16 +826,19 @@ function CallbacksTablePreMemo(props){
|
||||
}, [])
|
||||
const filterRow = (row) => {
|
||||
for(const [key,value] of Object.entries(filterOptions)){
|
||||
if(key === "agent"){
|
||||
if(!String(row.payload.payloadtype.name).toLowerCase().includes(String(value).toLowerCase())){
|
||||
return true;
|
||||
}
|
||||
if(key === "agent") {
|
||||
if (!gridValuePassesFilter(row.payload.payloadtype.name, value)) {
|
||||
return true;
|
||||
}
|
||||
}else if(key === "id"){
|
||||
if (!gridValuePassesFilter(row.display_id, value)) {
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if(!String(row[key]).toLowerCase().includes(String(value).toLowerCase())){
|
||||
if(!gridValuePassesFilter(row[key], value)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1001,9 +1010,10 @@ function CallbacksTablePreMemo(props){
|
||||
}, [])
|
||||
}, [callbacks, callbackLocalUpdates, sortData, filterOptions, columnVisibility, clickedCallbackID]);
|
||||
const onSubmitFilterOptions = (value) => {
|
||||
setFilterOptions({...filterOptions, [selectedColumn.key]: value });
|
||||
const nextFilterOptions = getUpdatedGridFilterOptions(filterOptions, selectedColumn.key, value);
|
||||
setFilterOptions(nextFilterOptions);
|
||||
try{
|
||||
updateSetting({setting_name: "callbacks_table_filter_options", value: {...filterOptions, [selectedColumn.key]: value }});
|
||||
updateSetting({setting_name: "callbacks_table_filter_options", value: nextFilterOptions});
|
||||
}catch(error){
|
||||
console.log("failed to save filter options");
|
||||
}
|
||||
@@ -1072,7 +1082,7 @@ function CallbacksTablePreMemo(props){
|
||||
sortIndicatorIndex={sortColumn}
|
||||
sortDirection={sortData.sortDirection}
|
||||
items={sortedData}
|
||||
rowHeight={GetComputedFontSize() + 10}
|
||||
rowHeight={GetComputedFontSize() + 15}
|
||||
onClickHeader={onClickHeader}
|
||||
onDoubleClickRow={onRowDoubleClick}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
@@ -1081,10 +1091,10 @@ function CallbacksTablePreMemo(props){
|
||||
{openContextMenu &&
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={openContextMenu}
|
||||
onClose={()=>{setOpenContextMenu(false);}}
|
||||
innerDialog={<MythicModifyStringDialog
|
||||
title='Filter Column'
|
||||
innerDialog={<GridColumnFilterDialog
|
||||
onSubmit={onSubmitFilterOptions}
|
||||
value={filterOptions[selectedColumn.key]}
|
||||
filterValue={filterOptions[selectedColumn.key]}
|
||||
selectedColumn={selectedColumn}
|
||||
onClose={() => {
|
||||
setOpenContextMenu(false);
|
||||
}}
|
||||
|
||||
+15
-8
@@ -36,6 +36,12 @@ import {
|
||||
} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {getIconColor, getIconName} from "./ResponseDisplayTable";
|
||||
import {CallbacksTableColumnsReorderDialog} from "./CallbacksTableColumnsReorderDialog";
|
||||
import {
|
||||
GridColumnFilterDialog,
|
||||
getUpdatedGridFilterOptions,
|
||||
gridValuePassesFilter,
|
||||
isGridColumnFilterActive
|
||||
} from "../../MythicComponents/MythicResizableGrid/GridColumnFilterDialog";
|
||||
|
||||
const updateFileComment = gql`
|
||||
mutation updateCommentMutation($mythictree_id: Int!, $comment: String!) {
|
||||
@@ -85,7 +91,7 @@ export const CallbacksTabsCustomFileBasedBrowserTable = (props) => {
|
||||
() =>
|
||||
columnOrder.reduce( (prev, cur) => {
|
||||
if(columnVisibility.visible.includes(cur.name)){
|
||||
if(filterOptions[cur.key] && String(filterOptions[cur.key]).length > 0){
|
||||
if(isGridColumnFilterActive(filterOptions[cur.key])){
|
||||
return [...prev, {...cur, filtered: true}];
|
||||
}else{
|
||||
return [...prev, {...cur}];
|
||||
@@ -176,9 +182,10 @@ export const CallbacksTabsCustomFileBasedBrowserTable = (props) => {
|
||||
return tempData;
|
||||
}, [allData, sortData]);
|
||||
const onSubmitFilterOptions = (value) => {
|
||||
setFilterOptions({...filterOptions, [selectedColumn.key]: value });
|
||||
const nextFilterOptions = getUpdatedGridFilterOptions(filterOptions, selectedColumn.key, value);
|
||||
setFilterOptions(nextFilterOptions);
|
||||
try{
|
||||
updateSetting({setting_name: `${props.treeConfig.name}_browser_filter_options`, value: {...filterOptions, [selectedColumn.key]: value }});
|
||||
updateSetting({setting_name: `${props.treeConfig.name}_browser_filter_options`, value: nextFilterOptions});
|
||||
}catch(error){
|
||||
console.log("failed to save filter options");
|
||||
}
|
||||
@@ -194,11 +201,11 @@ export const CallbacksTabsCustomFileBasedBrowserTable = (props) => {
|
||||
}
|
||||
for(const [key,value] of Object.entries(filterOptions)){
|
||||
if(["name_text", "comment"].includes(key)){
|
||||
if(!String(props.treeRootData[props.selectedFolderData.group][props.selectedFolderData.host][row][key]).toLowerCase().includes(value)){
|
||||
if(!gridValuePassesFilter(props.treeRootData[props.selectedFolderData.group][props.selectedFolderData.host][row][key], value)){
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if(!String(props.treeRootData[props.selectedFolderData.group][props.selectedFolderData.host][row].metadata[key]).toLowerCase().includes(value)){
|
||||
if(!gridValuePassesFilter(props.treeRootData[props.selectedFolderData.group][props.selectedFolderData.host][row].metadata[key], value)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -846,10 +853,10 @@ export const CallbacksTabsCustomFileBasedBrowserTable = (props) => {
|
||||
setOpenContextMenu(false);
|
||||
}}
|
||||
innerDialog={
|
||||
<MythicModifyStringDialog
|
||||
title='Filter Column'
|
||||
<GridColumnFilterDialog
|
||||
onSubmit={onSubmitFilterOptions}
|
||||
value={filterOptions[selectedColumn.key]}
|
||||
filterValue={filterOptions[selectedColumn.key]}
|
||||
selectedColumn={selectedColumn}
|
||||
onClose={() => {
|
||||
setOpenContextMenu(false);
|
||||
}}
|
||||
|
||||
@@ -41,6 +41,12 @@ import {
|
||||
useSetMythicSetting
|
||||
} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {CallbacksTableColumnsReorderDialog} from "./CallbacksTableColumnsReorderDialog";
|
||||
import {
|
||||
GridColumnFilterDialog,
|
||||
getUpdatedGridFilterOptions,
|
||||
gridValuePassesFilter,
|
||||
isGridColumnFilterActive
|
||||
} from "../../MythicComponents/MythicResizableGrid/GridColumnFilterDialog";
|
||||
|
||||
const getFileDownloadHistory = gql`
|
||||
query getFileDownloadHistory($full_path_text: String!, $host: String!, $group: [String!]) {
|
||||
@@ -107,7 +113,7 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
() =>
|
||||
columnOrder.reduce( (prev, cur) => {
|
||||
if(columnVisibility.visible.includes(cur.name)){
|
||||
if(filterOptions[cur.key] && String(filterOptions[cur.key]).length > 0){
|
||||
if(isGridColumnFilterActive(filterOptions[cur.key])){
|
||||
return [...prev, {...cur, filtered: true}];
|
||||
}else{
|
||||
return [...prev, {...cur}];
|
||||
@@ -197,9 +203,10 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
return tempData;
|
||||
}, [allData, sortData]);
|
||||
const onSubmitFilterOptions = (value) => {
|
||||
setFilterOptions({...filterOptions, [selectedColumn.key]: value });
|
||||
const nextFilterOptions = getUpdatedGridFilterOptions(filterOptions, selectedColumn.key, value);
|
||||
setFilterOptions(nextFilterOptions);
|
||||
try{
|
||||
updateSetting({setting_name: `file_browser_filter_options`, value: {...filterOptions,[selectedColumn.key]: value }});
|
||||
updateSetting({setting_name: `file_browser_filter_options`, value: nextFilterOptions});
|
||||
}catch(error){
|
||||
console.log("failed to save filter options");
|
||||
}
|
||||
@@ -214,7 +221,7 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
return true;
|
||||
}
|
||||
for(const [key,value] of Object.entries(filterOptions)){
|
||||
if(!String(props.treeRootData[props.selectedFolderData.group][props.selectedFolderData.host][row][key]).toLowerCase().includes(value)){
|
||||
if(!gridValuePassesFilter(props.treeRootData[props.selectedFolderData.group][props.selectedFolderData.host][row][key], value)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -735,10 +742,10 @@ export const CallbacksTabsFileBrowserTable = (props) => {
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={openContextMenu}
|
||||
onClose={()=>{setOpenContextMenu(false);}}
|
||||
innerDialog={
|
||||
<MythicModifyStringDialog
|
||||
title='Filter Column'
|
||||
<GridColumnFilterDialog
|
||||
onSubmit={onSubmitFilterOptions}
|
||||
value={filterOptions[selectedColumn.key]}
|
||||
filterValue={filterOptions[selectedColumn.key]}
|
||||
selectedColumn={selectedColumn}
|
||||
onClose={() => {
|
||||
setOpenContextMenu(false);
|
||||
}}
|
||||
|
||||
@@ -26,6 +26,12 @@ import {
|
||||
useSetMythicSetting
|
||||
} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {CallbacksTableColumnsReorderDialog} from "./CallbacksTableColumnsReorderDialog";
|
||||
import {
|
||||
GridColumnFilterDialog,
|
||||
getUpdatedGridFilterOptions,
|
||||
gridValuePassesFilter,
|
||||
isGridColumnFilterActive
|
||||
} from "../../MythicComponents/MythicResizableGrid/GridColumnFilterDialog";
|
||||
|
||||
const getPermissionsDataQuery = gql`
|
||||
query getPermissionsQuery($mythictree_id: Int!) {
|
||||
@@ -216,7 +222,7 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
() =>
|
||||
columnOrder.reduce( (prev, cur) => {
|
||||
if(columnVisibility.visible.includes(cur.name)){
|
||||
if(filterOptions[cur.key] && String(filterOptions[cur.key]).length > 0){
|
||||
if(isGridColumnFilterActive(filterOptions[cur.key])){
|
||||
return [...prev, {...cur, filtered: true}];
|
||||
}else{
|
||||
return [...prev, {...cur}];
|
||||
@@ -393,9 +399,10 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
return tempData;
|
||||
}, [allData, sortData]);
|
||||
const onSubmitFilterOptions = (value) => {
|
||||
setFilterOptions({...filterOptions, [selectedColumn.current.key]: value });
|
||||
const nextFilterOptions = getUpdatedGridFilterOptions(filterOptions, selectedColumn.current?.key, value);
|
||||
setFilterOptions(nextFilterOptions);
|
||||
try{
|
||||
updateSetting({setting_name: `process_browser_filter_options`, value: {...filterOptions,[selectedColumn.current.key]: value }});
|
||||
updateSetting({setting_name: `process_browser_filter_options`, value: nextFilterOptions});
|
||||
}catch(error){
|
||||
console.log("failed to save filter options");
|
||||
}
|
||||
@@ -422,11 +429,11 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
for(const [key,value] of Object.entries(filterOptions)){
|
||||
if(treeRootData[group][host][rowData.full_path_text] === undefined){return true}
|
||||
if(filterOptionInMetadata[key]){
|
||||
if(!String(treeRootData[group][host][rowData.full_path_text ]?.metadata[key]).toLowerCase().includes(value)){
|
||||
if(!gridValuePassesFilter(treeRootData[group][host][rowData.full_path_text ]?.metadata[key], value)){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if(!String(treeRootData[group][host][rowData.full_path_text][key]).toLowerCase().includes(value)){
|
||||
if(!gridValuePassesFilter(treeRootData[group][host][rowData.full_path_text][key], value)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -764,10 +771,10 @@ export const CallbacksTabsProcessBrowserTable = ({treeAdjMatrix, treeRootData, m
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={openContextMenu}
|
||||
onClose={()=>{setOpenContextMenu(false);}}
|
||||
innerDialog={
|
||||
<MythicModifyStringDialog
|
||||
title='Filter Column'
|
||||
<GridColumnFilterDialog
|
||||
onSubmit={onSubmitFilterOptions}
|
||||
value={filterOptions[selectedColumn.current]}
|
||||
filterValue={filterOptions[selectedColumn.current?.key]}
|
||||
selectedColumn={selectedColumn.current}
|
||||
onClose={() => {
|
||||
setOpenContextMenu(false);
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import {Button} from '@mui/material';
|
||||
import {MythicViewJSONAsTableDialog, MythicDialog, MythicModifyStringDialog} from '../../MythicComponents/MythicDialog';
|
||||
import {MythicViewJSONAsTableDialog, MythicDialog} from '../../MythicComponents/MythicDialog';
|
||||
import { MythicDisplayTextDialog } from '../../MythicComponents/MythicDisplayTextDialog';
|
||||
import { ResponseDisplayTableDialogTable } from './ResponseDisplayTableDialogTable';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
@@ -18,9 +18,14 @@ import {faList, faTrashAlt, faSkullCrossbones, faCamera, faSyringe, faFolder, fa
|
||||
faFileImage, faCopy, faBoxOpen, faFileAlt, faCirclePlus, faCheck, faSquareXmark, faRotate } from '@fortawesome/free-solid-svg-icons';
|
||||
import {Dropdown, DropdownMenuItem} from "../../MythicComponents/MythicNestedMenus";
|
||||
import {GetComputedFontSize} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {TableFilterDialog} from "./TableFilterDialog";
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import {MythicSectionHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
import {
|
||||
GridColumnFilterDialog,
|
||||
getUpdatedGridFilterOptions,
|
||||
gridValuePassesFilter,
|
||||
isGridColumnFilterActive
|
||||
} from "../../MythicComponents/MythicResizableGrid/GridColumnFilterDialog";
|
||||
|
||||
const onCopyToClipboard = (data) => {
|
||||
let result = copyStringToClipboard(data);
|
||||
@@ -142,7 +147,7 @@ const ResponseDisplayTableFontAwesomeEndIcon = ({cellData}) => {
|
||||
}
|
||||
const ResponseDisplayTableStringCell = ({cellData, rowData}) => {
|
||||
return (
|
||||
<div style={{...(cellData?.cellStyle || null), height: "100%"}}>
|
||||
<div className="mythic-response-table-cell" style={{...(cellData?.cellStyle || null), height: "100%"}}>
|
||||
<ResponseDisplayTableStringCellCopy cellData={cellData} />
|
||||
<ResponseDisplayTableFontAwesomeStartIcon cellData={cellData} />
|
||||
{cellData?.plaintextHoverText? (
|
||||
@@ -163,7 +168,7 @@ const ResponseDisplayTableStringCell = ({cellData, rowData}) => {
|
||||
}
|
||||
const ResponseDisplayTableNumberCell = ({cellData, rowData}) => {
|
||||
return (
|
||||
<div style={{...(cellData?.cellStyle || null), height: "100%"}}>
|
||||
<div className="mythic-response-table-cell" style={{...(cellData?.cellStyle || null), height: "100%"}}>
|
||||
{cellData?.copyIcon?
|
||||
<MythicStyledTooltip title={"Copy to clipboard"}>
|
||||
<IconButton onClick={() => onCopyToClipboard(cellData["plaintext"])} size="small">
|
||||
@@ -220,7 +225,7 @@ export const getStringSize = ({cellData}) => {
|
||||
}
|
||||
const ResponseDisplayTableSizeCell = ({cellData, rowData}) => {
|
||||
return (
|
||||
<div style={{...(cellData?.cellStyle || null), height: "100%"}}>
|
||||
<div className="mythic-response-table-cell" style={{...(cellData?.cellStyle || null), height: "100%"}}>
|
||||
{cellData?.plaintextHoverText? (
|
||||
<MythicStyledTooltip title={cellData.plaintextHoverText} >
|
||||
<pre style={{display: "inline-block", margin: 0}}>
|
||||
@@ -242,7 +247,7 @@ const getActionButtonClassName = (intent = "info") => {
|
||||
}
|
||||
const ResponseDisplayTableActionCell = ({cellData, callback_id, rowData}) => {
|
||||
return (
|
||||
<div style={{ height: "100%"}}>
|
||||
<div className="mythic-response-table-cell mythic-response-table-action-cell" style={{ height: "100%"}}>
|
||||
{cellData?.plaintext && cellData.plaintext}
|
||||
{cellData?.button && <ResponseDisplayTableActionCellButton cellData={cellData} callback_id={callback_id} />}
|
||||
</div>
|
||||
@@ -466,8 +471,8 @@ const createRowCells = ({row, rowIndex, headers, callback_id}) => {
|
||||
})
|
||||
}
|
||||
export const ResponseDisplayTable = ({table, callback_id, expand, task}) =>{
|
||||
const rowHeight = GetComputedFontSize() + 7;
|
||||
const headerHeight = GetComputedFontSize() + 32;
|
||||
const rowHeight = GetComputedFontSize() + 15;
|
||||
const headerHeight = GetComputedFontSize() + 35;
|
||||
const maxHeight = 375;
|
||||
const [dataHeight, setDataHeight] = React.useState(maxHeight);
|
||||
const [allData, setAllData] = React.useState([]);
|
||||
@@ -477,7 +482,7 @@ export const ResponseDisplayTable = ({table, callback_id, expand, task}) =>{
|
||||
const columns = React.useMemo(
|
||||
() =>
|
||||
table?.headers?.reduce( (prev, cur) => {
|
||||
if(filterOptions[cur.plaintext] && String(filterOptions[cur.plaintext]).length > 0){
|
||||
if(isGridColumnFilterActive(filterOptions[cur.plaintext])){
|
||||
return [...prev, {...cur, filtered: true}];
|
||||
}else{
|
||||
return [...prev, {...cur}];
|
||||
@@ -488,7 +493,7 @@ export const ResponseDisplayTable = ({table, callback_id, expand, task}) =>{
|
||||
const [sortData, setSortData] = React.useState({sortKey: null, sortType: null, sortDirection: "ASC"})
|
||||
const filterRow = (row) => {
|
||||
for(const [key,value] of Object.entries(filterOptions)){
|
||||
if(!String(row[key].plaintext).toLowerCase().includes(String(value).toLowerCase())){
|
||||
if(!gridValuePassesFilter(row[key]?.plaintext, value)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -592,7 +597,7 @@ export const ResponseDisplayTable = ({table, callback_id, expand, task}) =>{
|
||||
}, [sortedData, table?.headers, callback_id]
|
||||
);
|
||||
const onSubmitFilterOptions = (value) => {
|
||||
setFilterOptions({...filterOptions, [selectedColumn.key]: value });
|
||||
setFilterOptions(getUpdatedGridFilterOptions(filterOptions, selectedColumn.key, value));
|
||||
}
|
||||
|
||||
const filterOutButtonsFromRowData = (data) => {
|
||||
@@ -838,10 +843,10 @@ export const ResponseDisplayTable = ({table, callback_id, expand, task}) =>{
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={openContextMenu}
|
||||
onClose={()=>{setOpenContextMenu(false);}}
|
||||
innerDialog={
|
||||
<MythicModifyStringDialog
|
||||
title='Filter Column'
|
||||
<GridColumnFilterDialog
|
||||
onSubmit={onSubmitFilterOptions}
|
||||
value={filterOptions[selectedColumn.key]}
|
||||
filterValue={filterOptions[selectedColumn.key]}
|
||||
selectedColumn={selectedColumn}
|
||||
onClose={() => {
|
||||
setOpenContextMenu(false);
|
||||
}}
|
||||
|
||||
@@ -15,7 +15,8 @@ function ResponseDisplayTabsLabel(props) {
|
||||
</span>
|
||||
}
|
||||
className="mythic-response-tab"
|
||||
wrapped={true}
|
||||
title={typeof label === "string" ? label : undefined}
|
||||
wrapped={false}
|
||||
{...a11yProps(index)}
|
||||
{...other}
|
||||
/>
|
||||
@@ -64,7 +65,7 @@ export function ResponseDisplayTabs({ tabs, task, expand, displayType, output })
|
||||
TabIndicatorProps={{style: {
|
||||
display: "none",
|
||||
}}}
|
||||
aria-label='scrollable tabs'>
|
||||
aria-label='browser script response tabs'>
|
||||
{tabs.map((tab, index) => (
|
||||
<ResponseDisplayTabsLabel
|
||||
key={'tablabel' + task.id + index}
|
||||
|
||||
@@ -13,7 +13,7 @@ import Moment from 'react-moment';
|
||||
import moment from 'moment';
|
||||
import CastConnectedTwoToneIcon from '@mui/icons-material/CastConnectedTwoTone';
|
||||
import {snackActions} from "../../utilities/Snackbar";
|
||||
import {MythicDialog, MythicModifyStringDialog} from "../../MythicComponents/MythicDialog";
|
||||
import {MythicDialog} from "../../MythicComponents/MythicDialog";
|
||||
import OpenInNewTwoToneIcon from '@mui/icons-material/OpenInNewTwoTone';
|
||||
import IosShareIcon from '@mui/icons-material/IosShare';
|
||||
import {copyStringToClipboard} from "../../utilities/Clipboard";
|
||||
@@ -21,6 +21,12 @@ import {ipCompare} from "../Callbacks/CallbacksTable";
|
||||
import MythicResizableGrid from "../../MythicComponents/MythicResizableGrid";
|
||||
import {CallbacksTableStringCell} from "../Callbacks/CallbacksTableRow";
|
||||
import {GetComputedFontSize} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {
|
||||
GridColumnFilterDialog,
|
||||
getUpdatedGridFilterOptions,
|
||||
gridValuePassesFilter,
|
||||
isGridColumnFilterActive
|
||||
} from "../../MythicComponents/MythicResizableGrid/GridColumnFilterDialog";
|
||||
|
||||
const cancelEventGroupInstanceMutation = gql(`
|
||||
mutation cancelEventGroupInstanceMutation($eventgroupinstance_id: Int!){
|
||||
@@ -183,7 +189,7 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
{key: "operator", type: 'string', name: "Operator", inMetadata: true, fillWidth: true, disableSort: true,},
|
||||
{key: "cancel", type: 'string', name: "Action", width: 70, disableSort: true, disableFilter: true},
|
||||
]?.reduce( (prev, cur) => {
|
||||
if(filterOptions[cur.key] && String(filterOptions[cur.key]).length > 0){
|
||||
if(isGridColumnFilterActive(filterOptions[cur.key])){
|
||||
return [...prev, {...cur, filtered: true}];
|
||||
}else{
|
||||
return [...prev, {...cur}];
|
||||
@@ -212,11 +218,20 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
const filterRow = (row) => {
|
||||
for(const [key,value] of Object.entries(filterOptions)){
|
||||
if(key === "operator"){
|
||||
if(!String(row?.operator?.username).toLowerCase().includes(String(value).toLowerCase())){
|
||||
if(!gridValuePassesFilter(row?.operator?.username, value)){
|
||||
return true;
|
||||
}
|
||||
}else if(key === "eventgroup"){
|
||||
if(!gridValuePassesFilter(row?.eventgroup?.name, value)){
|
||||
return true;
|
||||
}
|
||||
}else if(key === "time"){
|
||||
const timeFilterValue = `${row?.created_at || ""} ${row?.created_at ? toLocalTime(row.created_at, me?.user?.view_utc_time) : ""} ${row?.end_timestamp || ""}`;
|
||||
if(!gridValuePassesFilter(timeFilterValue, value)){
|
||||
return true;
|
||||
}
|
||||
}else {
|
||||
if(!String(row[key]).toLowerCase().includes(String(value).toLowerCase())){
|
||||
if(!gridValuePassesFilter(row[key], value)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -380,7 +395,7 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
const [openContextMenu, setOpenContextMenu] = React.useState(false);
|
||||
const [selectedColumn, setSelectedColumn] = React.useState({});
|
||||
const onSubmitFilterOptions = (value) => {
|
||||
setFilterOptions({...filterOptions, [selectedColumn.key]: value });
|
||||
setFilterOptions(getUpdatedGridFilterOptions(filterOptions, selectedColumn.key, value));
|
||||
}
|
||||
const contextMenuOptions = [{
|
||||
name: 'Filter Column', type: "item", icon: null,
|
||||
@@ -420,10 +435,10 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={openContextMenu}
|
||||
onClose={()=>{setOpenContextMenu(false);}}
|
||||
innerDialog={
|
||||
<MythicModifyStringDialog
|
||||
title='Filter Column'
|
||||
<GridColumnFilterDialog
|
||||
onSubmit={onSubmitFilterOptions}
|
||||
value={filterOptions[selectedColumn.key]}
|
||||
filterValue={filterOptions[selectedColumn.key]}
|
||||
selectedColumn={selectedColumn}
|
||||
onClose={() => {
|
||||
setOpenContextMenu(false);
|
||||
}}
|
||||
|
||||
@@ -423,6 +423,7 @@ tspan {
|
||||
}
|
||||
.MythicResizableGrid-headerIndicator {
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
border: 1px solid ${(props) => alpha(props.theme.palette.text.primary, props.theme.palette.mode === "dark" ? 0.28 : 0.22)};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
@@ -430,15 +431,23 @@ tspan {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.95rem;
|
||||
font-family: inherit;
|
||||
height: 18px;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 18px;
|
||||
}
|
||||
.MythicResizableGrid-headerFilterIcon {
|
||||
background-color: ${(props) => alpha(props.theme.palette.mode === "dark" ? props.theme.palette.info.light : props.theme.palette.info.dark || props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.24 : 0.14)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.mode === "dark" ? props.theme.palette.info.light : props.theme.palette.info.dark || props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.72 : 0.5)};
|
||||
color: ${(props) => props.theme.palette.mode === "dark" ? props.theme.palette.info.light : props.theme.palette.info.dark || props.theme.palette.info.main};
|
||||
cursor: pointer;
|
||||
}
|
||||
.MythicResizableGrid-headerFilterIcon:hover {
|
||||
background-color: ${(props) => alpha(props.theme.palette.mode === "dark" ? props.theme.palette.info.light : props.theme.palette.info.dark || props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.34 : 0.22)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.mode === "dark" ? props.theme.palette.info.light : props.theme.palette.info.dark || props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.86 : 0.68)};
|
||||
}
|
||||
.MythicResizableGrid-headerSortIcon {
|
||||
background-color: ${(props) => alpha(props.theme.palette.mode === "dark" ? props.theme.palette.primary.light : props.theme.palette.primary.dark || props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.24 : 0.14)};
|
||||
@@ -593,6 +602,61 @@ tspan {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-grid-filter-dialog {
|
||||
background-color: ${(props) => props.theme.surfaces?.raised || props.theme.palette.background.paper};
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
}
|
||||
.mythic-grid-filter-dialog-title {
|
||||
background-color: ${(props) => props.theme.pageHeader?.main || props.theme.surfaces?.muted || props.theme.palette.background.default};
|
||||
background-image: ${(props) => `linear-gradient(90deg, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)} 0%, ${alpha(props.theme.palette.text.primary, props.theme.palette.mode === "dark" ? 0.045 : 0.035)} 100%)`};
|
||||
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.98rem !important;
|
||||
font-weight: 850 !important;
|
||||
line-height: 1.2 !important;
|
||||
padding: 0.85rem 1rem !important;
|
||||
}
|
||||
.mythic-grid-filter-dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.9rem;
|
||||
min-width: min(38rem, calc(100vw - 3rem));
|
||||
padding: 1rem !important;
|
||||
}
|
||||
.mythic-grid-filter-dialog-copy {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.78rem;
|
||||
font-weight: 650;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.mythic-grid-filter-dialog-mode-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.mythic-grid-filter-dialog-label {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.8rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
.mythic-grid-filter-dialog-fields {
|
||||
display: grid;
|
||||
gap: 0.85rem;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.mythic-grid-filter-dialog-actions {
|
||||
border-top: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
padding: 0.7rem 1rem !important;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.mythic-grid-filter-dialog-content {
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-grid-filter-dialog-fields {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.mythic-callback-interactCell {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
@@ -2674,15 +2738,22 @@ tspan {
|
||||
padding: 0.35rem;
|
||||
}
|
||||
.mythic-response-tabs-list {
|
||||
max-height: 34px;
|
||||
min-height: 34px;
|
||||
}
|
||||
.mythic-response-tabs-list .MuiTabs-flexContainer {
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.mythic-response-tabs-list .MuiTabs-scroller {
|
||||
overflow-x: auto !important;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
.mythic-response-tabs-list .MuiTabs-scrollButtons {
|
||||
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};
|
||||
flex: 0 0 30px;
|
||||
min-height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
@@ -2691,12 +2762,14 @@ tspan {
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px !important;
|
||||
color: ${(props) => props.theme.palette.text.secondary} !important;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.76rem;
|
||||
font-weight: 750;
|
||||
line-height: 1.2;
|
||||
max-width: min(18rem, 60vw);
|
||||
max-width: min(16rem, 55vw);
|
||||
min-height: 30px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 0.35rem 0.7rem;
|
||||
text-transform: none;
|
||||
}
|
||||
@@ -2711,6 +2784,7 @@ tspan {
|
||||
color: ${(props) => props.theme.palette.primary.main} !important;
|
||||
}
|
||||
.mythic-response-tab-label {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -2740,6 +2814,27 @@ tspan {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-response-table-cell {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
gap: 0.3rem;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-response-table-action-cell {
|
||||
gap: 0.45rem;
|
||||
}
|
||||
.mythic-response-table-cell pre {
|
||||
line-height: 1.2;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.mythic-response-table-cell .MuiIconButton-root {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.mythic-browser-scripts-table {
|
||||
max-width: 100%;
|
||||
min-width: 60rem;
|
||||
|
||||
Reference in New Issue
Block a user