mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
better json table displays and search table responses
This commit is contained in:
@@ -267,140 +267,216 @@ export function MythicModifyStringDialog(props) {
|
||||
);
|
||||
}
|
||||
|
||||
export function MythicViewJSONAsTableDialog(props) {
|
||||
const [comment, setComment] = React.useState([]);
|
||||
const [tableType, setTableType] = React.useState("dictionary");
|
||||
const [headers, setHeaders] = React.useState([]);
|
||||
useEffect( () => {
|
||||
let permissions = [];
|
||||
try{
|
||||
let permissionDict;
|
||||
if(props.value.constructor === Object){
|
||||
permissionDict = props.value;
|
||||
}else{
|
||||
permissionDict = JSON.parse(props.value);
|
||||
}
|
||||
|
||||
if(!Array.isArray(permissionDict) && typeof permissionDict !== 'string'){
|
||||
for(let key in permissionDict){
|
||||
if(permissionDict[key] && permissionDict[key].constructor === Object){
|
||||
// potentially have a nested dictionary here or array to become a dictionary, mark it
|
||||
permissions.push({"name": key, "value": permissionDict[key], new_table: true, is_dictionary: true, headers: ["Name", "Value"]});
|
||||
} else if(permissionDict[key] && Array.isArray(permissionDict[key])) {
|
||||
if (permissionDict[key].length === 1){
|
||||
if(permissionDict[key][0].constructor === Object){
|
||||
permissions.push({"name": key, "value": permissionDict[key][0], new_table: true, is_dictionary: true, headers: ["Name", "Value"]});
|
||||
|
||||
}else{
|
||||
permissions.push({"name": key, "value": JSON.stringify(permissionDict[key], null, 2)});
|
||||
}
|
||||
|
||||
} else if (permissionDict[key].length > 1) {
|
||||
if (permissionDict[key][0].constructor === Object) {
|
||||
let newHeaders = [];
|
||||
for(let i = 0; i < permissionDict[key].length; i++){
|
||||
for(let newKey in permissionDict[key][i]){
|
||||
if(!newHeaders.includes(newKey)){newHeaders.push(newKey)}
|
||||
}
|
||||
}
|
||||
newHeaders.sort()
|
||||
permissions.push({"name": key, "value": permissionDict[key], new_table: true, is_array: true, headers: newHeaders});
|
||||
} else {
|
||||
// it's an array, but not of dictionaries, so just stringify it
|
||||
permissions.push({"name": key, "value": JSON.stringify(permissionDict[key], null, 2)});
|
||||
}
|
||||
} else {
|
||||
permissions.push({"name": key, "value": JSON.stringify(permissionDict[key], null, 2)});
|
||||
}
|
||||
}else if(permissionDict[key] !== undefined && permissionDict[key] !== null){
|
||||
permissions.push({"name": key, "value": permissionDict[key]});
|
||||
}
|
||||
|
||||
setHeaders([props.leftColumn, props.rightColumn]);
|
||||
}
|
||||
}else{
|
||||
setTableType("array");
|
||||
if(permissionDict.length > 0){
|
||||
setHeaders(Object.keys(permissionDict[0]));
|
||||
permissions = permissionDict;
|
||||
}else{
|
||||
setHeaders([]);
|
||||
}
|
||||
}
|
||||
}catch(error){
|
||||
console.log(error);
|
||||
const isPlainObject = (value) => {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
const parseJSONDialogValue = (value) => {
|
||||
if(value === undefined || value === null){
|
||||
return value;
|
||||
}
|
||||
if(typeof value !== "string"){
|
||||
return value;
|
||||
}
|
||||
try{
|
||||
return JSON.parse(value);
|
||||
}catch(error){
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
const getJSONValueType = (value) => {
|
||||
if(value === null){return "null"}
|
||||
if(value === undefined){return "empty"}
|
||||
if(Array.isArray(value)){return "array"}
|
||||
if(isPlainObject(value)){return "object"}
|
||||
return typeof value;
|
||||
}
|
||||
|
||||
const getJSONValueCount = (value) => {
|
||||
if(Array.isArray(value)){return value.length}
|
||||
if(isPlainObject(value)){return Object.keys(value).length}
|
||||
if(value === undefined || value === null || value === ""){return 0}
|
||||
return 1;
|
||||
}
|
||||
|
||||
const getArrayObjectHeaders = (rows) => {
|
||||
const headers = [];
|
||||
rows.forEach((row) => {
|
||||
if(!isPlainObject(row)){return}
|
||||
Object.keys(row).forEach((key) => {
|
||||
if(!headers.includes(key)){
|
||||
headers.push(key);
|
||||
}
|
||||
setComment(permissions);
|
||||
}, [props.value, props.leftColumn, props.rightColumn]);
|
||||
});
|
||||
});
|
||||
return headers;
|
||||
}
|
||||
|
||||
const JSONTypeBadge = ({value}) => {
|
||||
const type = getJSONValueType(value);
|
||||
const count = getJSONValueCount(value);
|
||||
const label = type === "array" ? `${count} item${count === 1 ? "" : "s"}` :
|
||||
type === "object" ? `${count} field${count === 1 ? "" : "s"}` :
|
||||
type;
|
||||
return <span className={`mythic-json-type-badge mythic-json-type-${type}`}>{label}</span>
|
||||
}
|
||||
|
||||
const JSONPrimitiveValue = ({name, value, me}) => {
|
||||
const type = getJSONValueType(value);
|
||||
if(type === "null" || type === "empty" || value === ""){
|
||||
return <span className="mythic-json-value-empty">None</span>
|
||||
}
|
||||
if(type === "boolean"){
|
||||
return <span className={`mythic-json-value-boolean ${value ? "mythic-json-value-true" : "mythic-json-value-false"}`}>{value ? "True" : "False"}</span>
|
||||
}
|
||||
return <span className="mythic-json-value-primitive">{convertValueToContextValue(name, value, me)}</span>
|
||||
}
|
||||
|
||||
const JSONTableCellValue = ({name, value, me, depth}) => {
|
||||
if(Array.isArray(value) || isPlainObject(value)){
|
||||
return (
|
||||
<JSONTableValue
|
||||
label={name}
|
||||
value={value}
|
||||
me={me}
|
||||
depth={depth + 1}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <JSONPrimitiveValue name={name} value={value} me={me} />;
|
||||
}
|
||||
|
||||
const JSONTableValue = ({label, value, me, depth=0, leftColumn="Name", rightColumn="Value"}) => {
|
||||
const type = getJSONValueType(value);
|
||||
const showPanelHeader = Boolean(label) && depth === 0;
|
||||
if(type !== "array" && type !== "object"){
|
||||
return <JSONPrimitiveValue name={label || ""} value={value} me={me} />;
|
||||
}
|
||||
if(type === "object"){
|
||||
const entries = Object.entries(value);
|
||||
return (
|
||||
<div className={`mythic-json-panel ${depth === 0 ? "mythic-json-panel-root" : ""}`}>
|
||||
{showPanelHeader &&
|
||||
<div className="mythic-json-panel-header">
|
||||
<span className="mythic-json-panel-title">{label}</span>
|
||||
<JSONTypeBadge value={value} />
|
||||
</div>
|
||||
}
|
||||
{entries.length === 0 ? (
|
||||
<div className="mythic-json-empty-state">No fields to display.</div>
|
||||
) : (
|
||||
<TableContainer className="mythicElement mythic-json-table-wrap">
|
||||
<Table size="small" stickyHeader={depth === 0} style={{tableLayout: "fixed"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: depth === 0 ? "14rem" : "12rem"}}>{leftColumn}</TableCell>
|
||||
<TableCell>{rightColumn}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{entries.map(([key, entryValue]) => (
|
||||
<TableRow key={`${depth}-${key}`} hover>
|
||||
<TableCell className="mythic-json-key-cell">
|
||||
<div className="mythic-json-key-stack">
|
||||
<span className="mythic-json-key">{key}</span>
|
||||
<JSONTypeBadge value={entryValue} />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="mythic-json-value-cell">
|
||||
<JSONTableCellValue name={key} value={entryValue} me={me} depth={depth} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const objectHeaders = getArrayObjectHeaders(value);
|
||||
const isObjectArray = value.length > 0 && objectHeaders.length > 0 && value.every((row) => isPlainObject(row));
|
||||
return (
|
||||
<div className={`mythic-json-panel ${depth === 0 ? "mythic-json-panel-root" : ""}`}>
|
||||
{showPanelHeader &&
|
||||
<div className="mythic-json-panel-header">
|
||||
<span className="mythic-json-panel-title">{label}</span>
|
||||
<JSONTypeBadge value={value} />
|
||||
</div>
|
||||
}
|
||||
{value.length === 0 ? (
|
||||
<div className="mythic-json-empty-state">No items to display.</div>
|
||||
) : isObjectArray ? (
|
||||
<TableContainer className="mythicElement mythic-json-table-wrap">
|
||||
<Table size="small" stickyHeader={depth === 0} style={{tableLayout: "fixed", minWidth: `${Math.max(38, objectHeaders.length * 12)}rem`}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "4rem"}}>#</TableCell>
|
||||
{objectHeaders.map((header) => (
|
||||
<TableCell key={`array-header-${header}`} style={{width: "12rem"}}>{header}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{value.map((row, rowIndex) => (
|
||||
<TableRow key={`array-row-${rowIndex}`} hover>
|
||||
<TableCell className="mythic-json-index-cell">{rowIndex + 1}</TableCell>
|
||||
{objectHeaders.map((header) => (
|
||||
<TableCell key={`array-row-${rowIndex}-${header}`} className="mythic-json-value-cell">
|
||||
<JSONTableCellValue name={header} value={row[header]} me={me} depth={depth} />
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
) : (
|
||||
<TableContainer className="mythicElement mythic-json-table-wrap">
|
||||
<Table size="small" stickyHeader={depth === 0} style={{tableLayout: "fixed"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "4rem"}}>#</TableCell>
|
||||
<TableCell>Value</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{value.map((entryValue, rowIndex) => (
|
||||
<TableRow key={`array-value-row-${rowIndex}`} hover>
|
||||
<TableCell className="mythic-json-index-cell">{rowIndex + 1}</TableCell>
|
||||
<TableCell className="mythic-json-value-cell">
|
||||
<JSONTableCellValue name={`${label || "value"} ${rowIndex + 1}`} value={entryValue} me={me} depth={depth} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function MythicViewJSONAsTableDialog(props) {
|
||||
const parsedValue = React.useMemo(() => parseJSONDialogValue(props.value), [props.value]);
|
||||
const rootLabel = props.title || "JSON Data";
|
||||
return (
|
||||
<React.Fragment>
|
||||
<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"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{headers.map( (header, index) => (
|
||||
<TableCell key={'header' + index} style={index === 0 ? {width: "15%", wordBreak: "break-all"} : {wordBreak: "break-all"}}>{header}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{tableType === "dictionary" ? (
|
||||
comment.map( (element, index) => (
|
||||
<TableRow key={'row' + index} hover>
|
||||
<TableCell style={{wordBreak: "break-all"}}>{element.name}</TableCell>
|
||||
{element.new_table ?
|
||||
(
|
||||
<TableContainer className="mythicElement">
|
||||
<Table size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "scroll"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{element.headers.map( (header, index) => (
|
||||
<TableCell key={'eheader' + header + index} style={index === 0 ? {width: "15%", wordBreak: "break-all"} : {wordBreak: "break-all"}}>{header}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{element.is_dictionary ? (
|
||||
Object.keys(element.value).map( (key, dictIndex) => (
|
||||
<TableRow key={'element' + dictIndex + "dictheader"}>
|
||||
<TableCell style={{width: "30%", wordBreak: "break-all"}}>{key}</TableCell>
|
||||
<TableCell style={{wordBreak: "break-all", whiteSpace: "pre-wrap"}}>{convertValueToContextValue(key, element.value[key], props.me)}</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
): (
|
||||
element.value.map( (e, elementIndex) => (
|
||||
<TableRow>
|
||||
{element.headers.map( (header, headerIndex) => (
|
||||
<TableCell key={'element' + elementIndex + "header" + headerIndex} style={headerIndex === 0 ? {width: "15%", wordBreak: "break-all"} : {wordBreak: "break-all", whiteSpace: "pre-wrap"}}>{convertValueToContextValue(header, e[header], props.me)}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
:
|
||||
(<TableCell style={{wordBreak: "break-all", whiteSpace: "pre-wrap"}}>{convertValueToContextValue(element.name, element.value, props.me) }</TableCell>)
|
||||
}
|
||||
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
comment.map( (row, index) => (
|
||||
<TableRow key={'row' + index} hover>
|
||||
{Object.keys(row).map( (key) => (
|
||||
<TableCell key={"row" + index + "cell" + key} style={{wordBreak: "break-all"}}>{convertValueToContextValue(key, row[key], props.me)}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) }
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<MythicDraggableDialogTitle style={{wordBreak: "break-all", maxWidth: "100%"}}>
|
||||
<div className="mythic-json-title-row">
|
||||
<span>{rootLabel}</span>
|
||||
<JSONTypeBadge value={parsedValue} />
|
||||
</div>
|
||||
</MythicDraggableDialogTitle>
|
||||
<DialogContent dividers={true} className="mythic-dialog-body mythic-json-dialog-body">
|
||||
<JSONTableValue
|
||||
value={parsedValue}
|
||||
me={props.me}
|
||||
leftColumn={props.leftColumn || "Name"}
|
||||
rightColumn={props.rightColumn || "Value"}
|
||||
/>
|
||||
</DialogContent>
|
||||
<MythicDialogFooter>
|
||||
<MythicDialogButton onClick={props.onClose}>
|
||||
Close
|
||||
@@ -458,21 +534,25 @@ export function MythicViewObjectPropertiesAsTableDialog(props) {
|
||||
);
|
||||
}
|
||||
const convertValueToContextValue = (key, value, me) => {
|
||||
if( key.includes("time") ){
|
||||
const keyText = String(key || "").toLowerCase();
|
||||
if(value === undefined || value === null){
|
||||
return "";
|
||||
}
|
||||
if( keyText.includes("time") ){
|
||||
try{
|
||||
return TableRowDateCell({cellData: value, view_utc_time: me?.user?.view_utc_time})
|
||||
}catch(error){
|
||||
console.log("failed to parse metadata as date", key, value);
|
||||
return value;
|
||||
}
|
||||
} else if( key.includes("size") ){
|
||||
} else if( keyText.includes("size") ){
|
||||
try{
|
||||
return TableRowSizeCell({cellData: value})
|
||||
}catch(error){
|
||||
console.log("failed to parse metadata as size", key, value);
|
||||
return value;
|
||||
}
|
||||
} else if (value.constructor === Object) {
|
||||
} else if (isPlainObject(value)) {
|
||||
return JSON.stringify(value, null, 2);
|
||||
} else if (Array.isArray(value)){
|
||||
return JSON.stringify(value, null, 2);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {Typography, Link, IconButton} from '@mui/material';
|
||||
import {Link, IconButton} from '@mui/material';
|
||||
import { gql, useMutation} from '@apollo/client';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
@@ -11,6 +11,14 @@ import MythicStyledTableCell from '../../MythicComponents/MythicTableCell';
|
||||
import CleanHandsTwoToneIcon from '@mui/icons-material/CleanHandsTwoTone';
|
||||
import AddAlertTwoToneIcon from '@mui/icons-material/AddAlertTwoTone';
|
||||
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
|
||||
import {MythicStateChip} from "../../MythicComponents/MythicStateChip";
|
||||
|
||||
const singleLineCellStyle = {
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
};
|
||||
|
||||
const updateNeedsCleanupMutation = gql`
|
||||
mutation updateNeedsCleanupStatus($taskartifact_id: Int!, $needs_cleanup: Boolean!){
|
||||
@@ -70,15 +78,14 @@ export function ArtifactTable(props){
|
||||
}
|
||||
return (
|
||||
<TableContainer className="mythicElement" style={{height: "100%", overflowY: "auto"}}>
|
||||
<Table stickyHeader size="small" style={{}}>
|
||||
<Table stickyHeader size="small" style={{tableLayout: "fixed"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "4rem"}}>Cleanup</TableCell>
|
||||
<TableCell >Type</TableCell>
|
||||
<TableCell >Command</TableCell>
|
||||
<TableCell style={{width: "12rem"}}>Task</TableCell>
|
||||
<TableCell >Operator</TableCell>
|
||||
<TableCell >Host</TableCell>
|
||||
<TableCell style={{width: "7rem"}}>Cleanup</TableCell>
|
||||
<TableCell style={{width: "9rem"}}>Type</TableCell>
|
||||
<TableCell style={{width: "9rem"}}>Command</TableCell>
|
||||
<TableCell style={{width: "11rem"}}>Task</TableCell>
|
||||
<TableCell style={{width: "12rem"}}>Host</TableCell>
|
||||
<TableCell >Artifact</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
@@ -99,6 +106,9 @@ export function ArtifactTable(props){
|
||||
}
|
||||
|
||||
function ArtifactTableRow(props){
|
||||
const cleanupState = !props.needs_cleanup ? "neutral" : props.resolved ? "active" : "warning";
|
||||
const cleanupLabel = !props.needs_cleanup ? "None" : props.resolved ? "Done" : "Needed";
|
||||
const cleanupTooltip = !props.needs_cleanup ? "No cleanup needed" : props.resolved ? "Cleanup resolved" : "Artifact needs cleanup";
|
||||
const MarkNeedsCleanup = () => {
|
||||
props.MarkNeedsCleanup({id: props.id, needs_cleanup: true});
|
||||
}
|
||||
@@ -112,62 +122,75 @@ function ArtifactTableRow(props){
|
||||
<React.Fragment>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>
|
||||
{props.needs_cleanup && !props.resolved &&
|
||||
<MythicStyledTooltip title={"Artifact needs to be cleaned up"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-warning" onClick={MarkResolved} size="small">
|
||||
<CleanHandsTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<div className="mythic-search-result-action-row">
|
||||
{props.needs_cleanup && !props.resolved &&
|
||||
<MythicStyledTooltip title={"Mark artifact as cleaned up"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-success" onClick={MarkResolved} size="small">
|
||||
<CleanHandsTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
{props.needs_cleanup && props.resolved &&
|
||||
<MythicStyledTooltip title={"Mark artifact as unresolved"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-warning" onClick={MarkUnresolved} size="small">
|
||||
<CleanHandsTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
{!props.needs_cleanup &&
|
||||
<MythicStyledTooltip title={"Mark artifact as needs cleanup"} >
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-warning" onClick={MarkNeedsCleanup} size="small">
|
||||
<AddAlertTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
<MythicStyledTooltip title={cleanupTooltip}>
|
||||
<span>
|
||||
<MythicStateChip compact label={cleanupLabel} state={cleanupState} />
|
||||
</span>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
{props.needs_cleanup && props.resolved &&
|
||||
<MythicStyledTooltip title={"Successfully cleaned up artifact"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-success" onClick={MarkUnresolved} size="small">
|
||||
<CleanHandsTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
{!props.needs_cleanup &&
|
||||
<MythicStyledTooltip title={"Mark artifact as needs cleanup"} >
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-success" onClick={MarkNeedsCleanup} size="small">
|
||||
<AddAlertTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Typography variant="body2" >{props.base_artifact}</Typography>
|
||||
<div className="mythic-search-result-value" style={singleLineCellStyle} title={props.base_artifact}>
|
||||
{props.base_artifact}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell >
|
||||
<Typography variant="body2" >{props?.task?.command?.cmd}</Typography>
|
||||
<div className="mythic-search-result-value" style={singleLineCellStyle} title={props?.task?.command?.cmd}>
|
||||
{props?.task?.command?.cmd}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell style={{wordBreak: "break-all"}}>
|
||||
{props.task &&
|
||||
<>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/callbacks/" + props.task.callback.display_id}>
|
||||
C-{props.task.callback.display_id}
|
||||
</Link>{" / "}
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/task/" + props.task.display_id}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
<div className="mythic-search-result-stack">
|
||||
<div className="mythic-search-result-link-row">
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/callbacks/" + props.task.callback.display_id}>
|
||||
C-{props.task.callback.display_id}
|
||||
</Link>
|
||||
<span className="mythic-search-result-secondary">/</span>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/task/" + props.task.display_id}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
</div>
|
||||
{props.task?.callback?.mythictree_groups.length > 0 ? (
|
||||
<Typography variant="body2" style={{whiteSpace: "pre"}}>
|
||||
<b>Groups: </b>{"\n" + props?.task?.callback.mythictree_groups.join("\n")}
|
||||
</Typography>
|
||||
<div className="mythic-search-result-secondary">
|
||||
Groups: {props?.task?.callback.mythictree_groups.join(", ")}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Typography variant="body2" style={{ display: "inline-block"}}>{props?.task?.operator?.username || null}</Typography>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell >
|
||||
<Typography variant="body2" style={{ display: "inline-block"}}>{props.host}</Typography>
|
||||
<div className="mythic-search-result-value" style={singleLineCellStyle} title={props.host}>
|
||||
{props.host}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", display: "inline-block"}}>{props.artifact_text}</Typography>
|
||||
<div className="mythic-search-result-code">{props.artifact_text}</div>
|
||||
</MythicStyledTableCell>
|
||||
|
||||
</TableRow>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {IconButton, Typography, Link} from '@mui/material';
|
||||
import {IconButton, Link} from '@mui/material';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
@@ -15,6 +15,23 @@ import MythicStyledTableCell from '../../MythicComponents/MythicTableCell';
|
||||
import {TagsDisplay, ViewEditTags} from '../../MythicComponents/MythicTag';
|
||||
import { MythicStyledTooltip } from '../../MythicComponents/MythicStyledTooltip';
|
||||
|
||||
const singleLineCellStyle = {
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
};
|
||||
|
||||
const formatMetadataValue = (value) => {
|
||||
if(value === null || value === undefined){
|
||||
return "";
|
||||
}
|
||||
if(typeof value === "object"){
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const updateFileComment = gql`
|
||||
mutation updateCommentMutation($mythictree_id: Int!, $comment: String!){
|
||||
update_mythictree_by_pk(pk_columns: {id: $mythictree_id}, _set: {comment: $comment}) {
|
||||
@@ -24,7 +41,7 @@ mutation updateCommentMutation($mythictree_id: Int!, $comment: String!){
|
||||
}
|
||||
`;
|
||||
|
||||
export function CustomBrowserTable({rows, columns, me}){
|
||||
export function CustomBrowserTable({rows, columns=[], me}){
|
||||
const [entries, setEntries] = React.useState([]);
|
||||
useEffect( () => {
|
||||
setEntries([...rows]);
|
||||
@@ -39,17 +56,19 @@ export function CustomBrowserTable({rows, columns, me}){
|
||||
});
|
||||
setEntries(updates);
|
||||
}
|
||||
const metadataColumnWidth = columns.length > 0 ? "12rem" : undefined;
|
||||
const tableMinWidth = `${48 + (columns.length * 12)}rem`;
|
||||
return (
|
||||
<TableContainer className="mythicElement" style={{height: "100%", overflowY: "auto"}} >
|
||||
<Table stickyHeader size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableContainer className="mythicElement" style={{height: "100%", overflow: "auto"}} >
|
||||
<Table stickyHeader size="small" style={{tableLayout: "fixed", minWidth: tableMinWidth}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell >Name</TableCell>
|
||||
<TableCell style={{width: "24rem"}}>Entry</TableCell>
|
||||
{columns.map((column, index) => (
|
||||
<TableCell key={index}>{column.name}</TableCell>
|
||||
<TableCell key={index} style={{width: metadataColumnWidth}}>{column.name}</TableCell>
|
||||
))}
|
||||
<TableCell >Comment</TableCell>
|
||||
<TableCell >Tags</TableCell>
|
||||
<TableCell style={{width: "16rem"}}>Comment</TableCell>
|
||||
<TableCell style={{width: "8rem"}}>Tags</TableCell>
|
||||
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
@@ -95,54 +114,95 @@ function CustomBrowserTableRow(props){
|
||||
/>
|
||||
}
|
||||
<MythicStyledTableCell>
|
||||
<MythicStyledTooltip title="View metadata">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-info"
|
||||
size="small"
|
||||
onClick={() => setViewPermissionsDialogOpen(true)}
|
||||
>
|
||||
<PlaylistAddCheckIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}><b>Host: </b> {props.host}</Typography>
|
||||
{props.callback?.mythictree_groups.length > 0 ? (
|
||||
<Typography variant="body2" style={{whiteSpace: "pre"}}>
|
||||
<b>Groups: </b>{props?.callback.mythictree_groups.join(", ")}<br/>
|
||||
<b>Callback: </b>{
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
<div className="mythic-search-result-stack mythic-search-result-stack-spacious">
|
||||
<div className="mythic-search-result-action-row">
|
||||
<MythicStyledTooltip title="View metadata">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info"
|
||||
size="small"
|
||||
onClick={() => setViewPermissionsDialogOpen(true)}
|
||||
>
|
||||
<PlaylistAddCheckIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<span
|
||||
className="mythic-search-result-primary"
|
||||
style={{...singleLineCellStyle, textDecoration: props.deleted ? "line-through" : ""}}
|
||||
title={props.name_text || props.full_path_text}
|
||||
>
|
||||
{props.name_text || props.full_path_text}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mythic-search-result-inline">
|
||||
<span className="mythic-search-result-label">Host</span>
|
||||
<span className="mythic-search-result-value" style={singleLineCellStyle} title={props.host}>{props.host}</span>
|
||||
</div>
|
||||
{props.full_path_text && props.full_path_text !== props.name_text ? (
|
||||
<div className="mythic-search-result-code mythic-search-result-code-compact" title={props.full_path_text}>
|
||||
{props.full_path_text}
|
||||
</div>
|
||||
) : null}
|
||||
{props.callback ? (
|
||||
<div className="mythic-search-result-link-row">
|
||||
<span className="mythic-search-result-label">Callback</span>
|
||||
<Link color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/callbacks/" + props.callback.display_id}>
|
||||
C-{props.callback.display_id}
|
||||
</Link>
|
||||
}{", "}
|
||||
<b>Task: </b>{
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/task/" + props.task.display_id}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
}
|
||||
</Typography>
|
||||
) : null}
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", textDecoration: props.deleted ? "strike-through" : ""}}>{props.full_path_text}</Typography>
|
||||
{props.task ? (
|
||||
<React.Fragment>
|
||||
<span className="mythic-search-result-secondary">/</span>
|
||||
<span className="mythic-search-result-label">Task</span>
|
||||
<Link color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/task/" + props.task.display_id}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{props.callback?.mythictree_groups?.length > 0 ? (
|
||||
<div className="mythic-search-result-secondary">
|
||||
Groups: {props.callback.mythictree_groups.join(", ")}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
</MythicStyledTableCell>
|
||||
{props.columns.map((column, index) => (
|
||||
<MythicStyledTableCell key={index}>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", display: "inline-block"}}>{props.metadata[column.key]}</Typography>
|
||||
<div
|
||||
className="mythic-search-result-value"
|
||||
style={singleLineCellStyle}
|
||||
title={formatMetadataValue(props.metadata?.[column.key])}
|
||||
>
|
||||
{formatMetadataValue(props.metadata?.[column.key]) || <span className="mythic-search-result-secondary">None</span>}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
))}
|
||||
<MythicStyledTableCell>
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-info"
|
||||
onClick={() => setEditCommentDialogOpen(true)}
|
||||
size="small"
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", display: "inline-block"}}>{props.comment}</Typography>
|
||||
</MythicStyledTableCell>
|
||||
<div className="mythic-search-result-action-row">
|
||||
<MythicStyledTooltip title="Edit comment">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info"
|
||||
onClick={() => setEditCommentDialogOpen(true)}
|
||||
size="small"
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<span className="mythic-search-result-secondary" style={singleLineCellStyle} title={props.comment}>
|
||||
{props.comment || "No comment"}
|
||||
</span>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<ViewEditTags target_object={"mythictree_id"} target_object_id={props.id} me={me} />
|
||||
<TagsDisplay tags={props.tags} />
|
||||
<div className="mythic-custom-browser-tags-cell">
|
||||
<ViewEditTags target_object={"mythictree_id"} target_object_id={props.id} me={me} />
|
||||
<div className="mythic-custom-browser-tags-list">
|
||||
<TagsDisplay tags={props.tags} />
|
||||
</div>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
|
||||
</TableRow>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {Link} from '@mui/material';
|
||||
import {snackActions} from '../../utilities/Snackbar';
|
||||
import { meState } from '../../../cache';
|
||||
import {useReactiveVar} from '@apollo/client';
|
||||
import {IconButton, Typography} from '@mui/material';
|
||||
import {IconButton} from '@mui/material';
|
||||
import { toLocalTime } from '../../utilities/Time';
|
||||
import MythicStyledTableCell from '../../MythicComponents/MythicTableCell';
|
||||
import {b64DecodeUnicode} from "../Callbacks/ResponseDisplay";
|
||||
@@ -166,60 +166,77 @@ function KeylogTableRow(props){
|
||||
<React.Fragment>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank" href={"/new/callbacks/" + props.task.callback.display_id}>C-{props.task.callback.display_id}</Link>
|
||||
{" / "}
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank" href={"/new/task/" + props.task.display_id}>T-{props.task.display_id}</Link>
|
||||
<div className="mythic-search-result-stack">
|
||||
<div className="mythic-search-result-link-row">
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank" href={"/new/callbacks/" + props.task.callback.display_id}>C-{props.task.callback.display_id}</Link>
|
||||
<span className="mythic-search-result-secondary">/</span>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank" href={"/new/task/" + props.task.display_id}>T-{props.task.display_id}</Link>
|
||||
</div>
|
||||
|
||||
{props.task?.callback?.mythictree_groups.length > 0 ? (
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>
|
||||
<b>Groups: </b>{props?.task?.callback.mythictree_groups.join(", ")}
|
||||
</Typography>
|
||||
) : null}
|
||||
{props.task?.callback?.mythictree_groups.length > 0 ? (
|
||||
<div className="mythic-search-result-secondary">
|
||||
Groups: {props?.task?.callback.mythictree_groups.join(", ")}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell style={{wordBreak: "break-all"}}>
|
||||
<Typography variant="body2" ><b>User: </b>{props.user}</Typography>
|
||||
<Typography variant="body2" ><b>Host: </b>{props.task.callback.host}</Typography>
|
||||
<Typography variant="body2" ><b>Window:</b> {props.window} </Typography>
|
||||
<Typography variant="body2" ><b>Time: </b>{toLocalTime(props.timestamp, me?.user?.view_utc_time || false)}</Typography>
|
||||
<Typography variant="body2" style={{display: "inline-block"}}><b>View Window Together: </b></Typography>
|
||||
<MythicStyledTooltip title={"View current page data grouped together for this program"} tooltipStyle={{
|
||||
display: "inline-block"
|
||||
}}>
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-info"
|
||||
onClick={() => props.onGroupKeylogData(props.window, props.user, props.task.callback.host)}
|
||||
size="small"
|
||||
>
|
||||
<FullscreenIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
|
||||
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell >
|
||||
<MythicStyledTooltip title={"Copy to clipboard"} style={{display: "inline-block"}}>
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-info"
|
||||
onClick={() => onCopyToClipboard(keylogData)}
|
||||
size="small"
|
||||
>
|
||||
<ContentCopyIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", whiteSpace: "pre-wrap", display: "inline-block"}}>
|
||||
{keylogData.slice(0, 500)}
|
||||
{keylogData.length > 500 ? (
|
||||
<>
|
||||
{"..."}<br/>
|
||||
<div className="mythic-search-result-stack mythic-search-result-stack-spacious">
|
||||
<div className="mythic-search-result-inline">
|
||||
<span className="mythic-search-result-label">User</span>
|
||||
<span className="mythic-search-result-value">{props.user}</span>
|
||||
</div>
|
||||
<div className="mythic-search-result-inline">
|
||||
<span className="mythic-search-result-label">Host</span>
|
||||
<span className="mythic-search-result-value">{props.task.callback.host}</span>
|
||||
</div>
|
||||
<div className="mythic-search-result-inline">
|
||||
<span className="mythic-search-result-label">Window</span>
|
||||
<span className="mythic-search-result-value">{props.window}</span>
|
||||
</div>
|
||||
<div className="mythic-search-result-inline">
|
||||
<span className="mythic-search-result-label">Time</span>
|
||||
<span className="mythic-search-result-value">{toLocalTime(props.timestamp, me?.user?.view_utc_time || false)}</span>
|
||||
</div>
|
||||
<div className="mythic-search-result-action-row">
|
||||
<MythicStyledTooltip title={"View current page data grouped together for this program"}>
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-info"
|
||||
onClick={() => {setOpenDisplayKeylogData(true);}}
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info"
|
||||
onClick={() => props.onGroupKeylogData(props.window, props.user, props.task.callback.host)}
|
||||
size="small"
|
||||
>
|
||||
<OpenInNewIcon fontSize="small" />
|
||||
<FullscreenIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</>
|
||||
) : null}
|
||||
</MythicStyledTooltip>
|
||||
<span className="mythic-search-result-secondary">View window together</span>
|
||||
</div>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell >
|
||||
<div className="mythic-search-result-stack">
|
||||
<div className="mythic-search-result-action-row">
|
||||
<MythicStyledTooltip title={"Copy to clipboard"}>
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info"
|
||||
onClick={() => onCopyToClipboard(keylogData)}
|
||||
size="small"
|
||||
>
|
||||
<ContentCopyIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
{keylogData.length > 500 ? (
|
||||
<MythicStyledTooltip title={"Open full keylog data"}>
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info"
|
||||
onClick={() => {setOpenDisplayKeylogData(true);}}
|
||||
size="small"
|
||||
>
|
||||
<OpenInNewIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) : null}
|
||||
</div>
|
||||
<pre className="mythic-search-result-code mythic-search-result-code-compact">{keylogData.slice(0, 500)}{keylogData.length > 500 ? "..." : null}</pre>
|
||||
{openDisplayKeylogData &&
|
||||
<MythicDialog maxWidth={"100%"} fullWidth={true} open={openDisplayKeylogData} onClose={() => {setOpenDisplayKeylogData(false);}}
|
||||
innerDialog={
|
||||
@@ -227,7 +244,7 @@ function KeylogTableRow(props){
|
||||
title={"Full keylog data"} value={keylogData}/>
|
||||
} />
|
||||
}
|
||||
</Typography>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {IconButton, Typography, Link} from '@mui/material';
|
||||
import {IconButton, Link} from '@mui/material';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
@@ -14,6 +14,14 @@ import EditIcon from '@mui/icons-material/Edit';
|
||||
import MythicStyledTableCell from '../../MythicComponents/MythicTableCell';
|
||||
import {TagsDisplay, ViewEditTags} from '../../MythicComponents/MythicTag';
|
||||
import { MythicStyledTooltip } from '../../MythicComponents/MythicStyledTooltip';
|
||||
import {MythicStateChip} from "../../MythicComponents/MythicStateChip";
|
||||
|
||||
const singleLineCellStyle = {
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
};
|
||||
|
||||
const updateFileComment = gql`
|
||||
mutation updateCommentMutation($mythictree_id: Int!, $comment: String!){
|
||||
@@ -97,7 +105,7 @@ function ProcessTableRow(props){
|
||||
<MythicStyledTableCell>
|
||||
<MythicStyledTooltip title="View permissions data">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-info"
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info"
|
||||
size="small"
|
||||
onClick={() => setViewPermissionsDialogOpen(true)}
|
||||
>
|
||||
@@ -106,44 +114,67 @@ function ProcessTableRow(props){
|
||||
</MythicStyledTooltip>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", textDecoration: props.deleted ? "strike-through" : ""}}>{props.full_path_text}</Typography>
|
||||
<div
|
||||
className="mythic-search-result-value"
|
||||
style={{...singleLineCellStyle, textDecoration: props.deleted ? "line-through" : ""}}
|
||||
title={props.full_path_text}
|
||||
>
|
||||
{props.full_path_text}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}><b>Host: </b> {props.host}</Typography>
|
||||
{props.callback?.mythictree_groups.length > 0 ? (
|
||||
<Typography variant="body2" style={{whiteSpace: "pre"}}>
|
||||
<b>Groups: </b>{props?.callback.mythictree_groups.join(", ")}<br/>
|
||||
<b>Callback: </b>{
|
||||
<div className="mythic-search-result-stack">
|
||||
<div className="mythic-search-result-inline">
|
||||
<span className="mythic-search-result-label">Host</span>
|
||||
<span className="mythic-search-result-value">{props.host}</span>
|
||||
</div>
|
||||
{props.callback ? (
|
||||
<div className="mythic-search-result-link-row">
|
||||
<span className="mythic-search-result-label">Callback</span>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/callbacks/" + props.callback.display_id}>
|
||||
C-{props.callback.display_id}
|
||||
</Link>
|
||||
}<br/>
|
||||
<b>Task: </b>{
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/task/" + props.task.display_id}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
}
|
||||
</Typography>
|
||||
) : null}
|
||||
{props.task ? (
|
||||
<>
|
||||
<span className="mythic-search-result-secondary">/</span>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/task/" + props.task.display_id}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{props.callback?.mythictree_groups.length > 0 ? (
|
||||
<div className="mythic-search-result-secondary">
|
||||
Groups: {props?.callback.mythictree_groups.join(", ")}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
|
||||
<MythicStyledTableCell>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>{props.name_text}</Typography>
|
||||
{props.deleted &&
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>{" (deleted)"}</Typography>
|
||||
}
|
||||
<div className="mythic-search-result-stack">
|
||||
<div className="mythic-search-result-value" style={singleLineCellStyle} title={props.name_text}>
|
||||
{props.name_text}
|
||||
</div>
|
||||
{props.deleted &&
|
||||
<MythicStateChip compact label="Deleted" state="disabled" />
|
||||
}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-info"
|
||||
onClick={() => setEditCommentDialogOpen(true)}
|
||||
size="small"
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", display: "inline-block"}}>{props.comment}</Typography>
|
||||
<div className="mythic-search-result-action-row">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info"
|
||||
onClick={() => setEditCommentDialogOpen(true)}
|
||||
size="small"
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<span className="mythic-search-result-secondary">{props.comment || "No comment"}</span>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<ViewEditTags target_object={"mythictree_id"} target_object_id={props.id} me={me} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {IconButton, Typography, Link} from '@mui/material';
|
||||
import {IconButton, Link} from '@mui/material';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
@@ -16,6 +16,8 @@ import {getStringSize} from '../Callbacks/ResponseDisplayTable';
|
||||
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
|
||||
import {adjustOutput} from "../Eventing/EventGroupInstancesTable";
|
||||
import SpeedIcon from '@mui/icons-material/Speed';
|
||||
import MythicStyledTableCell from '../../MythicComponents/MythicTableCell';
|
||||
import {MythicStateChip} from "../../MythicComponents/MythicStateChip";
|
||||
|
||||
const toggleProxy = gql`
|
||||
mutation ToggleProxyMutation($callbackport_id: Int!, $action: String!){
|
||||
@@ -56,7 +58,7 @@ export function ProxySearchTable(props){
|
||||
<Table stickyHeader size="small" style={{tableLayout: "fixed"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "3.5rem"}}></TableCell>
|
||||
<TableCell style={{width: "8rem"}}>State</TableCell>
|
||||
<TableCell >User@Host</TableCell>
|
||||
<TableCell style={{width: "9rem"}}>Task Info</TableCell>
|
||||
<TableCell style={{width: "7rem"}}>Bound Port</TableCell>
|
||||
@@ -142,72 +144,98 @@ function ProxySearchTableRow(props){
|
||||
dialogText={confirmDialogText}
|
||||
/>
|
||||
}
|
||||
<TableCell>{props.deleted ? (
|
||||
<MythicStyledTooltip title="Start Proxy Port on Mythic Server">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-success"
|
||||
size="small"
|
||||
onClick={()=>{setOpenDeleteDialog(true);}}
|
||||
>
|
||||
<RestoreFromTrashIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) :
|
||||
(<MythicStyledTooltip title="Stop Proxy Port on Mythic Server">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-danger"
|
||||
size="small"
|
||||
onClick={()=>{setOpenDeleteDialog(true);}}
|
||||
>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)} </TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>
|
||||
<b>{props.callback.user}</b>@<i>{props.callback.host}</i>
|
||||
</Typography>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", display: "inline-block"}}>{props.callback.description}</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/callbacks/" + props.callback.display_id}>
|
||||
C-{props.callback.display_id}
|
||||
</Link>
|
||||
{" / "}
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/task/" + props.task.display_id}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>{props.local_port}</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-search-result-action-row">
|
||||
{props.deleted ? (
|
||||
<MythicStyledTooltip title="Start Proxy Port on Mythic Server">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-success"
|
||||
size="small"
|
||||
onClick={()=>{setOpenDeleteDialog(true);}}
|
||||
>
|
||||
<RestoreFromTrashIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) :
|
||||
(<MythicStyledTooltip title="Stop Proxy Port on Mythic Server">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-danger"
|
||||
size="small"
|
||||
onClick={()=>{setOpenDeleteDialog(true);}}
|
||||
>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)}
|
||||
<MythicStateChip compact label={props.deleted ? "Stopped" : "Running"} state={props.deleted ? "disabled" : "active"} />
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-search-result-stack">
|
||||
<div className="mythic-search-result-primary">{props.callback.user}@{props.callback.host}</div>
|
||||
{props.callback.description &&
|
||||
<div className="mythic-search-result-secondary">{props.callback.description}</div>
|
||||
}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-search-result-link-row">
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/callbacks/" + props.callback.display_id}>
|
||||
C-{props.callback.display_id}
|
||||
</Link>
|
||||
<span className="mythic-search-result-secondary">/</span>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/task/" + props.task.display_id}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<span className="mythic-search-result-code">{props.local_port}</span>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-search-result-stack">
|
||||
{props.remote_port !== 0 &&
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>{props.remote_ip}:{props.remote_port}</Typography>
|
||||
<div className="mythic-search-result-primary">{props.remote_ip}:{props.remote_port}</div>
|
||||
}
|
||||
{props.remote_port === 0 &&
|
||||
<div className="mythic-search-result-secondary">No remote endpoint</div>
|
||||
}
|
||||
{props.username !== "" &&
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>
|
||||
<b>Authentication Required</b><br/>
|
||||
Username: <b>{props.username}</b><br/>
|
||||
Password: <b>{props.password}</b>
|
||||
</Typography>
|
||||
<div className="mythic-search-result-stack">
|
||||
<div className="mythic-search-result-inline">
|
||||
<span className="mythic-search-result-label">Auth</span>
|
||||
<span className="mythic-search-result-value">{props.username}</span>
|
||||
</div>
|
||||
<div className="mythic-search-result-inline">
|
||||
<span className="mythic-search-result-label">Password</span>
|
||||
<span className="mythic-search-result-value">{props.password}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<b><MythicStyledTooltip title={"Rx is bytes Mythic received from the agent"}>{"Rx: "}</MythicStyledTooltip></b>
|
||||
{getStringSize({cellData: {"plaintext": String(props.bytes_received)}})}
|
||||
<br/>
|
||||
<b><MythicStyledTooltip
|
||||
title={"Tx is bytes Mythic sent to the agent"}>{"Tx: "}</MythicStyledTooltip></b>
|
||||
{getStringSize({cellData: {"plaintext": String(props.bytes_sent)}})}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>{props.port_type}</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-search-result-stack">
|
||||
<span className="mythic-search-result-metric">
|
||||
<MythicStyledTooltip title={"Rx is bytes Mythic received from the agent"}>
|
||||
<span className="mythic-search-result-metric-label">Rx</span>
|
||||
</MythicStyledTooltip>
|
||||
<span className="mythic-search-result-value">{getStringSize({cellData: {"plaintext": String(props.bytes_received)}})}</span>
|
||||
</span>
|
||||
<span className="mythic-search-result-metric">
|
||||
<MythicStyledTooltip title={"Tx is bytes Mythic sent to the agent"}>
|
||||
<span className="mythic-search-result-metric-label">Tx</span>
|
||||
</MythicStyledTooltip>
|
||||
<span className="mythic-search-result-value">{getStringSize({cellData: {"plaintext": String(props.bytes_sent)}})}</span>
|
||||
</span>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<MythicStateChip compact label={props.port_type} state="neutral" />
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Moment filter={(newTime) => adjustOutput(props, newTime)} interval={1000}
|
||||
parse={"YYYY-MM-DDTHH:mm:ss.SSSSSSZ"}
|
||||
withTitle
|
||||
@@ -216,12 +244,12 @@ function ProxySearchTableRow(props){
|
||||
>
|
||||
{props.updated_at + "Z"}
|
||||
</Moment>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{props.remote_port !== 0 &&
|
||||
<MythicStyledTooltip title={"Test Remote Connection"} >
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-success"
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-success"
|
||||
size="small"
|
||||
onClick={onTestProxy}
|
||||
>
|
||||
@@ -229,7 +257,7 @@ function ProxySearchTableRow(props){
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
</TableCell>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
@@ -150,6 +150,7 @@ const SearchTabCustomBrowserSearchPanel = (props) => {
|
||||
props.onCommentSearch({search:adjustedSearch, searchHost:adjustedSearchHost, offset: 0})
|
||||
break;
|
||||
case "Tag":
|
||||
case "Tags":
|
||||
props.onTagSearch({search:adjustedSearch, searchHost:adjustedSearchHost, offset: 0});
|
||||
break;
|
||||
case "Metadata":
|
||||
@@ -178,10 +179,11 @@ const SearchTabCustomBrowserSearchPanel = (props) => {
|
||||
setSearch(queryParams.get("search"));
|
||||
adjustedSearch = queryParams.get("search");
|
||||
}
|
||||
if(queryParams.has("searchField") && searchFieldOptions.includes(queryParams.get("searchField"))){
|
||||
setSearchField(queryParams.get("searchField"));
|
||||
props.onChangeSearchField(queryParams.get("searchField"));
|
||||
adjustedSearchField = queryParams.get("searchField");
|
||||
if(queryParams.has("searchField") && (searchFieldOptions.includes(queryParams.get("searchField")) || queryParams.get("searchField") === "Tag")){
|
||||
const nextSearchField = queryParams.get("searchField") === "Tag" ? "Tags" : queryParams.get("searchField");
|
||||
setSearchField(nextSearchField);
|
||||
props.onChangeSearchField(nextSearchField);
|
||||
adjustedSearchField = nextSearchField;
|
||||
}else{
|
||||
setSearchField("Name");
|
||||
props.onChangeSearchField("Name");
|
||||
@@ -252,15 +254,19 @@ export const SearchTabCustomBrowserPanel = (props) =>{
|
||||
onCommentSearch({search, searchHost, offset: 0});
|
||||
break;
|
||||
case "Tag":
|
||||
case "Tags":
|
||||
onTagSearch({search, searchHost, offset: 0});
|
||||
break;
|
||||
case "Metadata":
|
||||
onOtherSearch({search, searchHost, offset: 0});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
const handleSearchResults = (data) => {
|
||||
snackActions.dismiss();
|
||||
if(searchField === "Tag"){
|
||||
if(searchField === "Tag" || searchField === "Tags"){
|
||||
setTotalCount(data.tag_aggregate.aggregate.count);
|
||||
setProcessData(data?.tag?.map(t => t.mythictree) || []);
|
||||
} else {
|
||||
@@ -361,6 +367,7 @@ export const SearchTabCustomBrowserPanel = (props) =>{
|
||||
onCommentSearch({search, searchHost:searchHost, offset: (value - 1) * fetchLimit});
|
||||
break;
|
||||
case "Tag":
|
||||
case "Tags":
|
||||
onTagSearch({search, searchHost:searchHost, offset: (value - 1) * fetchLimit});
|
||||
break;
|
||||
case "Metadata":
|
||||
@@ -385,7 +392,7 @@ export const SearchTabCustomBrowserPanel = (props) =>{
|
||||
changeSearchParam={props.changeSearchParam}/>
|
||||
<div style={{overflowY: "auto", flexGrow: 1}}>
|
||||
{processData.length > 0 ? (
|
||||
<CustomBrowserTable rows={processData} columns={selectedBrowser.columns} />) : (
|
||||
<CustomBrowserTable rows={processData} columns={selectedBrowser.columns} me={props.me} />) : (
|
||||
<MythicSearchEmptyState
|
||||
compact
|
||||
description="Adjust the custom browser query or selected browser field and search again."
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {Link, IconButton, Typography} from '@mui/material';
|
||||
import {Link, IconButton} from '@mui/material';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
@@ -21,6 +21,31 @@ import {HostFileDialog} from "../Payloads/HostFileDialog";
|
||||
import PublicIcon from '@mui/icons-material/Public';
|
||||
import {DetailedPayloadTable} from "../Payloads/DetailedPayloadTable";
|
||||
import InfoIconOutline from '@mui/icons-material/InfoOutlined';
|
||||
import {getReadableTextColor, isValidHexColor} from "../../MythicComponents/MythicColorInput";
|
||||
|
||||
const singleLineCellStyle = {
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
};
|
||||
|
||||
const formatTagData = (data) => {
|
||||
if(data === null || data === undefined){
|
||||
return "";
|
||||
}
|
||||
if(typeof data === "string"){
|
||||
return data;
|
||||
}
|
||||
return JSON.stringify(data, null, 2);
|
||||
};
|
||||
|
||||
const safeDecode = (value) => {
|
||||
if(!value){
|
||||
return "";
|
||||
}
|
||||
return b64DecodeUnicode(value);
|
||||
};
|
||||
|
||||
export function TagTable(props){
|
||||
const [tags, setTags] = React.useState([]);
|
||||
@@ -33,12 +58,12 @@ export function TagTable(props){
|
||||
}
|
||||
return (
|
||||
<TableContainer className="mythicElement" style={{height: "100%", overflowY: "auto"}} >
|
||||
<Table stickyHeader size="small" style={{"tableLayout": "fixed", "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<Table stickyHeader size="small" style={{tableLayout: "fixed"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "4rem"}}>Delete</TableCell>
|
||||
<TableCell style={{width: "20%"}}>TagType</TableCell>
|
||||
<TableCell style={{width: "8rem"}}>Source</TableCell>
|
||||
<TableCell style={{width: "14rem"}}>Tag Type</TableCell>
|
||||
<TableCell style={{width: "10rem"}}>Source</TableCell>
|
||||
<TableCell>Tagged Element Information</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
@@ -56,14 +81,15 @@ export function TagTable(props){
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
|
||||
function TagTableRow(props){
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = React.useState(false);
|
||||
const [deleteTag] = useMutation(deleteTagMutation, {
|
||||
onCompleted: (data) => {
|
||||
onCompleted: () => {
|
||||
snackActions.success("Successfully deleted tag");
|
||||
props.onDelete(props.id);
|
||||
},
|
||||
onError: (data) => {
|
||||
onError: () => {
|
||||
snackActions.error("Failed to delete tag");
|
||||
}
|
||||
})
|
||||
@@ -84,21 +110,71 @@ function TagTableRow(props){
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell >
|
||||
<div style={{float: "left"}}><TagsDisplay expand={true} tags={[props]} /></div>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-tag-search-tag-cell"><TagsDisplay expand={true} tags={[props]} /></div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>{props.source}</MythicStyledTableCell>
|
||||
<MythicStyledTableCell style={{wordBreak: "break-all"}}><TagTableRowElement {...props} /></MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-search-result-value" style={singleLineCellStyle} title={props.source}>
|
||||
{props.source}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell><TagTableRowElement {...props} /></MythicStyledTableCell>
|
||||
</TableRow>
|
||||
{openDeleteDialog &&
|
||||
<MythicConfirmDialog onClose={() => {setOpenDeleteDialog(false);}}
|
||||
onSubmit={onAcceptDelete} open={openDeleteDialog}
|
||||
acceptText={ "Remove" }/>
|
||||
}
|
||||
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
const TagElementPanel = ({type, summary, actions, children}) => (
|
||||
<div className="mythic-tag-search-element-card">
|
||||
<div className="mythic-tag-search-element-header">
|
||||
<div className="mythic-search-result-action-row">
|
||||
<span className="mythic-tag-search-element-type">{type}</span>
|
||||
{summary}
|
||||
</div>
|
||||
{actions ? <div className="mythic-search-result-action-row">{actions}</div> : null}
|
||||
</div>
|
||||
<div className="mythic-tag-search-details-grid">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const TagDetailItem = ({label, children, wide=false, code=false, title}) => (
|
||||
<div className={wide ? "mythic-tag-search-detail mythic-tag-search-detail-wide" : "mythic-tag-search-detail"}>
|
||||
<div className="mythic-search-result-label">{label}</div>
|
||||
{code ? (
|
||||
<pre className="mythic-search-result-code mythic-tag-search-code">{children}</pre>
|
||||
) : (
|
||||
<div className="mythic-search-result-value" title={title}>
|
||||
{children || <span className="mythic-search-result-secondary">None</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const CallbackSummary = ({callback, includeDescription=false}) => {
|
||||
if(!callback){
|
||||
return null;
|
||||
}
|
||||
const safeColor = isValidHexColor(callback.color) ? callback.color : "";
|
||||
const callbackStyle = safeColor ? {
|
||||
backgroundColor: safeColor,
|
||||
borderColor: safeColor,
|
||||
color: getReadableTextColor(safeColor),
|
||||
} : {};
|
||||
return (
|
||||
<span className="mythic-tag-search-callback-summary" style={callbackStyle}>
|
||||
{callback.user}{callback.integrity_level > 2 ? "*" : ""}@{callback.host}
|
||||
{includeDescription && callback.description ? ` - ${callback.description}` : ""}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
function TagTableRowElement(props){
|
||||
const [viewPermissionsDialogOpen, setViewPermissionsDialogOpen] = React.useState(false);
|
||||
const [openDetailedView, setOpenDetailedView] = React.useState(false);
|
||||
@@ -106,136 +182,69 @@ function TagTableRowElement(props){
|
||||
const getElement = () => {
|
||||
if(props.task) {
|
||||
return (
|
||||
<TableContainer className="mythicElement" >
|
||||
<Table stickyHeader size="small" style={{"tableLayout": "fixed", "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<TableCell style={{width: "20%"}}>Element Type</TableCell>
|
||||
<TableCell><b>Task</b></TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell >Task / Callback</TableCell>
|
||||
<TableCell>
|
||||
<Link href={"/new/task/" + props.task.display_id} color="textPrimary" target={"_blank"}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
{" / "}
|
||||
<Link href={"/new/callbacks/" + props.task.callback.display_id} color="textPrimary" target={"_blank"}>
|
||||
C-{props.task.callback.display_id}
|
||||
</Link>
|
||||
<div style={{border: props.task.callback.color === "" ? "" : `1px solid ${props.task.callback.color}`}}>
|
||||
{props.task.callback.user}{props.task.callback.integrity_level > 2 ? "*" : ""}@{props.task.callback.host}
|
||||
{" - "}{props.task.callback.description}
|
||||
</div>
|
||||
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Command</TableCell>
|
||||
<TableCell>
|
||||
{props.task.command_name} {props.task.display_params}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Comment</TableCell>
|
||||
<TableCell>{props.task.comment}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Data</TableCell>
|
||||
<TableCell>{JSON.stringify(props.data, null, 2)}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TagElementPanel
|
||||
type="Task"
|
||||
summary={
|
||||
<div className="mythic-search-result-link-row">
|
||||
<Link href={"/new/task/" + props.task.display_id} color="textPrimary" target={"_blank"}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
<span className="mythic-search-result-secondary">/</span>
|
||||
<Link href={"/new/callbacks/" + props.task.callback.display_id} color="textPrimary" target={"_blank"}>
|
||||
C-{props.task.callback.display_id}
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<TagDetailItem label="Callback" wide>
|
||||
<CallbackSummary callback={props.task.callback} includeDescription />
|
||||
</TagDetailItem>
|
||||
<TagDetailItem label="Command" wide>
|
||||
<span className="mythic-search-result-code mythic-tag-search-inline-code">
|
||||
{props.task.command_name} {props.task.display_params}
|
||||
</span>
|
||||
</TagDetailItem>
|
||||
<TagDetailItem label="Comment" wide>{props.task.comment}</TagDetailItem>
|
||||
<TagDetailItem label="Tag Data" wide code>{formatTagData(props.data)}</TagDetailItem>
|
||||
</TagElementPanel>
|
||||
)
|
||||
} else if(props.credential) {
|
||||
return (
|
||||
<TableContainer className="mythicElement" >
|
||||
<Table stickyHeader size="small" style={{"tableLayout": "fixed", "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<TableCell style={{width: "20%"}}>Element Type</TableCell>
|
||||
<TableCell><b>Credential</b></TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Account</TableCell>
|
||||
<TableCell>{props.credential.account}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Realm</TableCell>
|
||||
<TableCell>{props.credential.realm}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Comment</TableCell>
|
||||
<TableCell>{props.credential.comment}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Credential Type</TableCell>
|
||||
<TableCell>{props.credential.type}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover style={{whiteSpace: "pre"}}>
|
||||
<TableCell>Credential</TableCell>
|
||||
<TableCell>{props.credential.credential_text}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Data</TableCell>
|
||||
<TableCell>{JSON.stringify(props.data, null, 2)}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TagElementPanel type="Credential">
|
||||
<TagDetailItem label="Account">{props.credential.account}</TagDetailItem>
|
||||
<TagDetailItem label="Realm">{props.credential.realm}</TagDetailItem>
|
||||
<TagDetailItem label="Type">{props.credential.type}</TagDetailItem>
|
||||
<TagDetailItem label="Comment" wide>{props.credential.comment}</TagDetailItem>
|
||||
<TagDetailItem label="Credential" wide code>{props.credential.credential_text}</TagDetailItem>
|
||||
<TagDetailItem label="Tag Data" wide code>{formatTagData(props.data)}</TagDetailItem>
|
||||
</TagElementPanel>
|
||||
)
|
||||
} else if(props.mythictree) {
|
||||
const treeType = props.mythictree.tree_type === "file" ? "File Browser" : "Process Browser";
|
||||
return (
|
||||
<TableContainer className="mythicElement" >
|
||||
<Table stickyHeader size="small" style={{"tableLayout": "fixed", "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<TableCell style={{width: "15%"}}>Element Type</TableCell>
|
||||
<TableCell>{props.mythictree.tree_type === "file" ? <b>File Browser</b> : <b>Process Browser</b>}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell >Name</TableCell>
|
||||
<TableCell>{props.mythictree.name_text}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>
|
||||
{props.mythictree.tree_type === "file" ? (
|
||||
"Path"
|
||||
) : (
|
||||
"PID"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{props.mythictree.full_path_text}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Host</TableCell>
|
||||
<TableCell>{props.mythictree.host}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Comment</TableCell>
|
||||
<TableCell>{props.mythictree.comment}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Metadata</TableCell>
|
||||
<TableCell>
|
||||
<MythicStyledTooltip title="View metadata">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-info"
|
||||
size="small"
|
||||
onClick={() => setViewPermissionsDialogOpen(true)}
|
||||
>
|
||||
<PlaylistAddCheckIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Data</TableCell>
|
||||
<TableCell>{JSON.stringify(props.data, null, 2)}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<React.Fragment>
|
||||
<TagElementPanel
|
||||
type={treeType}
|
||||
actions={
|
||||
<MythicStyledTooltip title="View metadata">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info"
|
||||
size="small"
|
||||
onClick={() => setViewPermissionsDialogOpen(true)}
|
||||
>
|
||||
<PlaylistAddCheckIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
>
|
||||
<TagDetailItem label="Name">{props.mythictree.name_text}</TagDetailItem>
|
||||
<TagDetailItem label={props.mythictree.tree_type === "file" ? "Path" : "PID"}>
|
||||
{props.mythictree.full_path_text}
|
||||
</TagDetailItem>
|
||||
<TagDetailItem label="Host">{props.mythictree.host}</TagDetailItem>
|
||||
<TagDetailItem label="Comment" wide>{props.mythictree.comment}</TagDetailItem>
|
||||
<TagDetailItem label="Tag Data" wide code>{formatTagData(props.data)}</TagDetailItem>
|
||||
</TagElementPanel>
|
||||
{viewPermissionsDialogOpen &&
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={viewPermissionsDialogOpen}
|
||||
onClose={()=>{setViewPermissionsDialogOpen(false);}}
|
||||
@@ -245,160 +254,118 @@ function TagTableRowElement(props){
|
||||
onClose={()=>{setViewPermissionsDialogOpen(false);}} />}
|
||||
/>
|
||||
}
|
||||
</TableContainer>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else if(props.filemetum) {
|
||||
const fileKind = props.filemetum.is_screenshot ? "Screenshot" : props.filemetum.is_download_from_agent ? "File Download" : "File Upload";
|
||||
const filename = safeDecode(props.filemetum.filename_text);
|
||||
const remotePath = safeDecode(props.filemetum.full_remote_path_text);
|
||||
return (
|
||||
<TableContainer className="mythicElement" >
|
||||
<Table stickyHeader size="small" style={{"tableLayout": "fixed", "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<TableCell style={{width: "15%"}}>Element Type</TableCell>
|
||||
<TableCell><b>{props.filemetum.is_screenshot ? "Screenshot" : props.filemetum.is_download_from_agent ? "File Download" : "File Upload"}</b></TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell >Filename</TableCell>
|
||||
<TableCell>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" download underline="always" target="_blank" href={"/direct/download/" + props.filemetum.agent_file_id}>{b64DecodeUnicode(props.filemetum.filename_text)}</Link>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Hash</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>MD5: {props.filemetum.md5}</Typography>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>SHA1: {props.filemetum.sha1}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Comment</TableCell>
|
||||
<TableCell>{props.filemetum.comment}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Full Remote Path</TableCell>
|
||||
<TableCell>
|
||||
{props.filemetum.host}<br/>
|
||||
{b64DecodeUnicode(props.filemetum.full_remote_path_text)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>File Hosting</TableCell>
|
||||
<TableCell>
|
||||
<MythicStyledTooltip title={"Host Payload Through C2"} >
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-info"
|
||||
size="small"
|
||||
onClick={()=>{setOpenHostDialog(true);}}
|
||||
>
|
||||
<PublicIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Data</TableCell>
|
||||
<TableCell>{JSON.stringify(props.data, null, 2)}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
|
||||
{openHostDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openHostDialog}
|
||||
onClose={()=>{setOpenHostDialog(false);}}
|
||||
innerDialog={<HostFileDialog file_uuid={props.filemetum.agent_file_id}
|
||||
file_name={props.filemetum.full_remote_path_text === "" ? b64DecodeUnicode(props.filemetum.filename_text) : b64DecodeUnicode(props.filemetum.full_remote_path_text)}
|
||||
onClose={()=>{setOpenHostDialog(false);}} />}
|
||||
/>
|
||||
<React.Fragment>
|
||||
<TagElementPanel
|
||||
type={fileKind}
|
||||
actions={
|
||||
<MythicStyledTooltip title={"Host Payload Through C2"} >
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info"
|
||||
size="small"
|
||||
onClick={()=>{setOpenHostDialog(true);}}
|
||||
>
|
||||
<PublicIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
</Table>
|
||||
</TableContainer>
|
||||
>
|
||||
<TagDetailItem label="Filename" wide>
|
||||
<Link color="textPrimary" download underline="always" target="_blank" href={"/direct/download/" + props.filemetum.agent_file_id}>
|
||||
{filename}
|
||||
</Link>
|
||||
</TagDetailItem>
|
||||
<TagDetailItem label="Hash" wide>
|
||||
<div className="mythic-search-result-stack">
|
||||
<span>MD5: {props.filemetum.md5}</span>
|
||||
<span>SHA1: {props.filemetum.sha1}</span>
|
||||
</div>
|
||||
</TagDetailItem>
|
||||
<TagDetailItem label="Comment" wide>{props.filemetum.comment}</TagDetailItem>
|
||||
<TagDetailItem label="Full Remote Path" wide code>
|
||||
{`${props.filemetum.host || ""}${remotePath ? "\n" + remotePath : ""}`}
|
||||
</TagDetailItem>
|
||||
<TagDetailItem label="Tag Data" wide code>{formatTagData(props.data)}</TagDetailItem>
|
||||
</TagElementPanel>
|
||||
{openHostDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openHostDialog}
|
||||
onClose={()=>{setOpenHostDialog(false);}}
|
||||
innerDialog={<HostFileDialog file_uuid={props.filemetum.agent_file_id}
|
||||
file_name={props.filemetum.full_remote_path_text === "" ? filename : remotePath}
|
||||
onClose={()=>{setOpenHostDialog(false);}} />}
|
||||
/>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}else if(props.keylog_id) {
|
||||
return <Typography ><b>Keylog: </b> {props.keylog_id}</Typography>
|
||||
return (
|
||||
<TagElementPanel type="Keylog">
|
||||
<TagDetailItem label="Keylog ID">{props.keylog_id}</TagDetailItem>
|
||||
<TagDetailItem label="Tag Data" wide code>{formatTagData(props.data)}</TagDetailItem>
|
||||
</TagElementPanel>
|
||||
)
|
||||
}else if(props.payload){
|
||||
return (
|
||||
<TableContainer className="mythicElement" >
|
||||
<Table stickyHeader size="small" style={{"tableLayout": "fixed", "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<TableCell style={{width: "20%"}}>Element Type</TableCell>
|
||||
<TableCell><b>Payload</b></TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell >Filename</TableCell>
|
||||
<TableCell>
|
||||
{b64DecodeUnicode(props.payload.filemetum.filename_text)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell >UUID</TableCell>
|
||||
<TableCell>
|
||||
{props.payload.uuid}
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-info"
|
||||
onClick={()=>setOpenDetailedView(true)}
|
||||
size="small"
|
||||
>
|
||||
<InfoIconOutline fontSize="small" />
|
||||
</IconButton>
|
||||
{openDetailedView &&
|
||||
<MythicDialog fullWidth={true} maxWidth="lg" open={openDetailedView}
|
||||
onClose={()=>{setOpenDetailedView(false);}}
|
||||
innerDialog={<DetailedPayloadTable {...props.payload} payload_id={props.payload.id} onClose={()=>{setOpenDetailedView(false);}} />}
|
||||
/>}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell>{props.payload.description}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Data</TableCell>
|
||||
<TableCell>{JSON.stringify(props.data, null, 2)}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TagElementPanel
|
||||
type="Payload"
|
||||
summary={props.payload.payloadtype?.name ? <span className="mythic-search-result-secondary">{props.payload.payloadtype.name}</span> : null}
|
||||
actions={
|
||||
<MythicStyledTooltip title="View payload details">
|
||||
<IconButton
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info"
|
||||
onClick={()=>setOpenDetailedView(true)}
|
||||
size="small"
|
||||
>
|
||||
<InfoIconOutline fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
>
|
||||
<TagDetailItem label="Filename" wide>{safeDecode(props.payload.filemetum?.filename_text)}</TagDetailItem>
|
||||
<TagDetailItem label="UUID" wide code>{props.payload.uuid}</TagDetailItem>
|
||||
<TagDetailItem label="Description" wide>{props.payload.description}</TagDetailItem>
|
||||
<TagDetailItem label="Tag Data" wide code>{formatTagData(props.data)}</TagDetailItem>
|
||||
{openDetailedView &&
|
||||
<MythicDialog fullWidth={true} maxWidth="lg" open={openDetailedView}
|
||||
onClose={()=>{setOpenDetailedView(false);}}
|
||||
innerDialog={<DetailedPayloadTable {...props.payload} payload_id={props.payload.id} onClose={()=>{setOpenDetailedView(false);}} />}
|
||||
/>}
|
||||
</TagElementPanel>
|
||||
)
|
||||
}else if(props.taskartifact_id) {
|
||||
return <Typography><b>Artifact: </b> {props.taskartifact_id}</Typography>
|
||||
return (
|
||||
<TagElementPanel type="Artifact">
|
||||
<TagDetailItem label="Artifact ID">{props.taskartifact_id}</TagDetailItem>
|
||||
<TagDetailItem label="Tag Data" wide code>{formatTagData(props.data)}</TagDetailItem>
|
||||
</TagElementPanel>
|
||||
)
|
||||
}else if(props.callback){
|
||||
return (
|
||||
<TableContainer className="mythicElement" >
|
||||
<Table stickyHeader size="small" style={{"tableLayout": "fixed", "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<TableCell style={{width: "20%"}}>Element Type</TableCell>
|
||||
<TableCell><b>Callback</b></TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell >Callback</TableCell>
|
||||
<TableCell>
|
||||
<Link href={"/new/callbacks/" + props.callback.display_id} color="textPrimary" target={"_blank"}>
|
||||
C-{props.callback.display_id}
|
||||
</Link>
|
||||
<div style={{border: props.callback.color === "" ? "" : `1px solid ${props.callback.color}`}}>
|
||||
{props.callback.user}{props.callback.integrity_level > 2 ? "*" : ""}@{props.callback.host}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell>{props.callback.description}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>IP</TableCell>
|
||||
<TableCell>{props.callback.ip}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Data</TableCell>
|
||||
<TableCell>{JSON.stringify(props.data, null, 2)}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TagElementPanel
|
||||
type="Callback"
|
||||
summary={
|
||||
<Link href={"/new/callbacks/" + props.callback.display_id} color="textPrimary" target={"_blank"}>
|
||||
C-{props.callback.display_id}
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<TagDetailItem label="Callback" wide>
|
||||
<CallbackSummary callback={props.callback} />
|
||||
</TagDetailItem>
|
||||
<TagDetailItem label="Description" wide>{props.callback.description}</TagDetailItem>
|
||||
<TagDetailItem label="IP">{props.callback.ip}</TagDetailItem>
|
||||
<TagDetailItem label="Tag Data" wide code>{formatTagData(props.data)}</TagDetailItem>
|
||||
</TagElementPanel>
|
||||
)
|
||||
|
||||
} else {
|
||||
console.log("unknown id for tag", props)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return getElement()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {IconButton, Typography, Link} from '@mui/material';
|
||||
import {IconButton, Link} from '@mui/material';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
@@ -19,6 +19,7 @@ import {TokenDescriptionDialog} from './TokenDescriptionDialog';
|
||||
import {TokenUserDialog} from './TokenUserDialog';
|
||||
import { MythicStyledTooltip } from '../../MythicComponents/MythicStyledTooltip';
|
||||
import MythicStyledTableCell from '../../MythicComponents/MythicTableCell';
|
||||
import {MythicStateChip} from "../../MythicComponents/MythicStateChip";
|
||||
|
||||
const updateCredentialDeleted = gql`
|
||||
mutation updateCredentialDeletedMutation($token_id: Int!, $deleted: Boolean!){
|
||||
@@ -78,7 +79,7 @@ export function TokenTable(props){
|
||||
<Table stickyHeader size="small" style={{"maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "5rem"}}>Visibility</TableCell>
|
||||
<TableCell style={{width: "9rem"}}>State</TableCell>
|
||||
<TableCell >User</TableCell>
|
||||
<TableCell >TokenId</TableCell>
|
||||
<TableCell >Description</TableCell>
|
||||
@@ -140,18 +141,25 @@ function TokenTableRow(props){
|
||||
<MythicConfirmDialog onClose={() => {setOpenDeleteDialog(false);}} onSubmit={onAcceptDelete} open={openDeleteDialog} acceptText={props.deleted ? "Restore" : "Hide" }/>
|
||||
}
|
||||
|
||||
<MythicStyledTableCell>{props.deleted ? (
|
||||
<MythicStyledTooltip title="Restore Token for use in Tasking">
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-danger" size="small" onClick={()=>{setOpenDeleteDialog(true);}}><VisibilityOffIcon fontSize="small" /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) : (
|
||||
<MythicStyledTooltip title="Delete Token so it can't be used in Tasking">
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-success" size="small" onClick={()=>{setOpenDeleteDialog(true);}}><VisibilityIcon fontSize="small" /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)} </MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-info" onClick={() => setEditUserDialog(true)} size="small"><EditIcon fontSize="small" /></IconButton>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", display: "inline-block"}}>{props.user}</Typography>
|
||||
<div className="mythic-search-result-action-row">
|
||||
{props.deleted ? (
|
||||
<MythicStyledTooltip title="Restore Token for use in Tasking">
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-success" size="small" onClick={()=>{setOpenDeleteDialog(true);}}><VisibilityOffIcon fontSize="small" /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) : (
|
||||
<MythicStyledTooltip title="Hide Token so it can't be used in Tasking">
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-warning" size="small" onClick={()=>{setOpenDeleteDialog(true);}}><VisibilityIcon fontSize="small" /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)}
|
||||
<MythicStateChip compact label={props.deleted ? "Hidden" : "Available"} state={props.deleted ? "disabled" : "active"} />
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-search-result-action-row">
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" onClick={() => setEditUserDialog(true)} size="small"><EditIcon fontSize="small" /></IconButton>
|
||||
<span className="mythic-search-result-primary">{props.user}</span>
|
||||
</div>
|
||||
|
||||
{editUserDialog && <MythicDialog fullWidth={true} maxWidth="md" open={editUserDialog}
|
||||
onClose={()=>{setEditUserDialog(false);}}
|
||||
@@ -160,18 +168,22 @@ function TokenTableRow(props){
|
||||
}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell >
|
||||
<MythicStyledTooltip title="View Token Information">
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-info" size="small" onClick={()=>{setViewTokenDialog(true);}}><ConfirmationNumberIcon fontSize="small" /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", display: "inline-block"}}>{props.token_id}</Typography>
|
||||
<div className="mythic-search-result-action-row">
|
||||
<MythicStyledTooltip title="View Token Information">
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={()=>{setViewTokenDialog(true);}}><ConfirmationNumberIcon fontSize="small" /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<span className="mythic-search-result-code">{props.token_id}</span>
|
||||
</div>
|
||||
{viewTokenDialog && <MythicDialog fullWidth={true} maxWidth="md" open={viewTokenDialog}
|
||||
onClose={()=>{setViewTokenDialog(false);}}
|
||||
innerDialog={<TaskTokenDialog token_id={props.id} onClose={()=>{setViewTokenDialog(false);}} />}
|
||||
/>}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-info" onClick={() => setEditDescriptionDialog(true)} size="small"><EditIcon fontSize="small" /></IconButton>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all", display: "inline-block"}}>{props.description}</Typography>
|
||||
<div className="mythic-search-result-action-row">
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" onClick={() => setEditDescriptionDialog(true)} size="small"><EditIcon fontSize="small" /></IconButton>
|
||||
<span className="mythic-search-result-secondary">{props.description || "No description"}</span>
|
||||
</div>
|
||||
|
||||
{editDescriptionDialog && <MythicDialog fullWidth={true} maxWidth="md" open={editDescriptionDialog}
|
||||
onClose={()=>{setEditDescriptionDialog(false);}}
|
||||
@@ -180,22 +192,26 @@ function TokenTableRow(props){
|
||||
}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/task/" + props.task.display_id}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>{props.callbacktokens?.map( (cbt) => (
|
||||
<React.Fragment key={"callbacktoken-" + props.id + "-" + cbt.id}>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank" key={"callbacklink" + cbt.callback.display_id + "row" + props.id}
|
||||
href={"/new/callbacks/" + cbt.callback.display_id}>
|
||||
C-{cbt.callback.display_id}
|
||||
<div className="mythic-search-result-link-row">
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/task/" + props.task.display_id}>
|
||||
T-{props.task.display_id}
|
||||
</Link>
|
||||
{" "}
|
||||
</React.Fragment>
|
||||
))|| null
|
||||
}</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>{props.host}</MythicStyledTableCell>
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-search-result-link-row">
|
||||
{props.callbacktokens?.length > 0 ? props.callbacktokens.map( (cbt) => (
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" target="_blank" key={"callbacklink" + cbt.callback.display_id + "row" + props.id}
|
||||
href={"/new/callbacks/" + cbt.callback.display_id}>
|
||||
C-{cbt.callback.display_id}
|
||||
</Link>
|
||||
)) : <span className="mythic-search-result-secondary">No callbacks</span>}
|
||||
</div>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-search-result-primary">{props.host}</div>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
@@ -3280,6 +3280,152 @@ tspan {
|
||||
padding: 0.55rem 0.65rem;
|
||||
white-space: pre;
|
||||
}
|
||||
.mythic-json-title-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.55rem;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-json-dialog-body.MuiDialogContent-root {
|
||||
gap: 0.65rem;
|
||||
max-height: min(72vh, 54rem);
|
||||
overflow: auto;
|
||||
padding: 0.85rem !important;
|
||||
}
|
||||
.mythic-json-panel {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.035)" : "rgba(0,0,0,0.018)"};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
padding: 0.55rem;
|
||||
}
|
||||
.mythic-json-panel-root {
|
||||
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.paper};
|
||||
}
|
||||
.mythic-json-panel-header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-json-panel-title {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-json-type-badge {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.055)" : "rgba(0,0,0,0.035)"};
|
||||
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};
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
min-height: 22px;
|
||||
padding: 0.22rem 0.42rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-json-type-object,
|
||||
.mythic-json-type-array {
|
||||
background-color: ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.15 : 0.08)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.34 : 0.22)};
|
||||
color: ${(props) => props.theme.palette.info.main};
|
||||
}
|
||||
.mythic-json-table-wrap {
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
max-height: 48vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.mythic-json-table-wrap .MuiTableCell-root {
|
||||
border-bottom-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
vertical-align: top;
|
||||
}
|
||||
.mythic-json-table-wrap .MuiTableHead-root .MuiTableCell-root {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
.mythic-json-key-cell {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.018)" : "rgba(0,0,0,0.012)"};
|
||||
}
|
||||
.mythic-json-key-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.28rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-json-key {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-json-value-cell {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.35;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.mythic-json-index-cell {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.74rem;
|
||||
font-weight: 800;
|
||||
text-align: right;
|
||||
}
|
||||
.mythic-json-value-primitive {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.35;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.mythic-json-value-empty {
|
||||
color: ${(props) => props.theme.palette.text.disabled};
|
||||
font-size: 0.76rem;
|
||||
font-style: italic;
|
||||
}
|
||||
.mythic-json-value-boolean {
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: inline-flex;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
padding: 0.22rem 0.45rem;
|
||||
}
|
||||
.mythic-json-value-true {
|
||||
background-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.14 : 0.08)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.34 : 0.24)};
|
||||
color: ${(props) => props.theme.palette.success.main};
|
||||
}
|
||||
.mythic-json-value-false {
|
||||
background-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.14 : 0.08)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.34 : 0.24)};
|
||||
color: ${(props) => props.theme.palette.warning.main};
|
||||
}
|
||||
.mythic-json-empty-state {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.78rem;
|
||||
font-style: italic;
|
||||
padding: 0.45rem 0.2rem;
|
||||
}
|
||||
.MuiDialogActions-root,
|
||||
.mythic-dialog-actions {
|
||||
align-items: center;
|
||||
@@ -5931,6 +6077,243 @@ tspan {
|
||||
.mythic-table-row-icon-action-hover-danger:hover {
|
||||
color: ${(props) => props.theme.palette.error.main} !important;
|
||||
}
|
||||
.mythic-search-result-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.22rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-search-result-stack-spacious {
|
||||
gap: 0.38rem;
|
||||
}
|
||||
.mythic-search-result-inline {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-search-result-inline-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.mythic-search-result-primary {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.82rem;
|
||||
font-weight: 750;
|
||||
line-height: 1.35;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-search-result-secondary {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.74rem;
|
||||
line-height: 1.35;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-search-result-label {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.68rem;
|
||||
font-weight: 750;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.mythic-search-result-value {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.35;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-search-result-code {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.035)"};
|
||||
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.primary};
|
||||
display: block;
|
||||
font-family: ${(props) => props.theme.typography.fontFamilyMonospace || "monospace"};
|
||||
font-size: 0.76rem;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
padding: 0.42rem 0.5rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.mythic-search-result-code-compact {
|
||||
max-height: 7.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-search-result-metric {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.028)"};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: inline-flex;
|
||||
gap: 0.28rem;
|
||||
min-height: 24px;
|
||||
padding: 0.18rem 0.45rem;
|
||||
}
|
||||
.mythic-search-result-metric-label {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.68rem;
|
||||
font-weight: 750;
|
||||
}
|
||||
.mythic-search-result-action-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-search-result-link-row {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.2rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-tag-search-tag-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-tag-search-tag-cell .MuiChip-root {
|
||||
flex: 0 1 auto;
|
||||
float: none !important;
|
||||
height: 20px !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
.mythic-tag-search-tag-cell .MuiChip-label {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
min-width: 0;
|
||||
overflow: hidden !important;
|
||||
padding-left: 0.45rem;
|
||||
padding-right: 0.45rem;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.mythic-tag-search-element-card {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.035)" : "rgba(0,0,0,0.018)"};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
padding: 0.48rem 0.55rem;
|
||||
}
|
||||
.mythic-tag-search-element-header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-tag-search-element-type {
|
||||
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.16 : 0.08)};
|
||||
border: 1px solid ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.34 : 0.22)};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
display: inline-flex;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 760;
|
||||
line-height: 1.25;
|
||||
padding: 0.16rem 0.45rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-tag-search-details-grid {
|
||||
display: grid;
|
||||
gap: 0.4rem 0.65rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-tag-search-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-tag-search-detail-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.mythic-tag-search-code {
|
||||
font-size: 0.72rem;
|
||||
max-height: 7.5rem;
|
||||
overflow: auto;
|
||||
}
|
||||
.mythic-tag-search-inline-code {
|
||||
display: inline-block;
|
||||
padding: 0.24rem 0.42rem;
|
||||
}
|
||||
.mythic-tag-search-callback-summary {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.035)"};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: inline-block;
|
||||
font-size: 0.76rem;
|
||||
line-height: 1.35;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 0.28rem 0.45rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-custom-browser-tags-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-custom-browser-tags-cell .MuiIconButton-root {
|
||||
align-items: center !important;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.025)"} !important;
|
||||
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} !important;
|
||||
display: inline-flex !important;
|
||||
flex: 0 0 auto;
|
||||
float: none !important;
|
||||
height: 22px;
|
||||
justify-content: center !important;
|
||||
padding: 0 !important;
|
||||
transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
|
||||
width: 22px;
|
||||
}
|
||||
.mythic-custom-browser-tags-cell .MuiIconButton-root:hover {
|
||||
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.16 : 0.09)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.44 : 0.3)};
|
||||
color: ${(props) => props.theme.palette.primary.main} !important;
|
||||
}
|
||||
.mythic-custom-browser-tags-cell .MuiIconButton-root svg {
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
.mythic-custom-browser-tags-list {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.2rem;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-custom-browser-tags-list .MuiChip-root {
|
||||
flex: 0 0 auto;
|
||||
float: none !important;
|
||||
height: 18px !important;
|
||||
max-width: 5.5rem;
|
||||
}
|
||||
.mythic-custom-browser-tags-list .MuiChip-label {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
min-width: 0;
|
||||
overflow: hidden !important;
|
||||
padding-left: 0.35rem;
|
||||
padding-right: 0.35rem;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.mythic-eventing-workspace {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
|
||||
Reference in New Issue
Block a user