mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
tag updates
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import {HexColorInput} from 'react-colorful';
|
||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||
import Popper from '@mui/material/Popper';
|
||||
import {HexColorInput, HexColorPicker} from 'react-colorful';
|
||||
|
||||
export const isValidHexColor = (color) => {
|
||||
if(typeof color !== "string"){
|
||||
@@ -27,54 +29,92 @@ export const getReadableTextColor = (backgroundColor) => {
|
||||
}
|
||||
|
||||
export const MythicColorSwatchInput = ({color, label, onChange, inputWidth = "96px", sx = {}}) => {
|
||||
const [pickerOpen, setPickerOpen] = React.useState(false);
|
||||
const swatchRef = React.useRef(null);
|
||||
const safeColor = isValidHexColor(color) ? color : "#000000";
|
||||
return (
|
||||
<Box sx={{display: "flex", alignItems: "center", gap: 1, minWidth: 0, ...sx}}>
|
||||
<Box
|
||||
component="input"
|
||||
type="color"
|
||||
value={safeColor}
|
||||
onChange={(evt) => onChange(evt.target.value)}
|
||||
aria-label={label}
|
||||
sx={{
|
||||
width: 34,
|
||||
height: 30,
|
||||
flex: "0 0 auto",
|
||||
p: 0,
|
||||
border: "none",
|
||||
backgroundColor: "transparent",
|
||||
cursor: "pointer",
|
||||
"&::-webkit-color-swatch-wrapper": {p: 0},
|
||||
"&::-webkit-color-swatch": {
|
||||
<ClickAwayListener onClickAway={() => setPickerOpen(false)}>
|
||||
<Box sx={{display: "flex", alignItems: "center", gap: 1, minWidth: 0, position: "relative", ...sx}}>
|
||||
<Box
|
||||
component="button"
|
||||
type="button"
|
||||
ref={swatchRef}
|
||||
onClick={(evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
setPickerOpen((open) => !open);
|
||||
}}
|
||||
aria-label={label}
|
||||
aria-expanded={pickerOpen}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
backgroundColor: "transparent",
|
||||
border: "1px solid",
|
||||
borderColor: "divider",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
"&::-moz-color-swatch": {
|
||||
border: "1px solid",
|
||||
borderColor: "divider",
|
||||
cursor: "pointer",
|
||||
display: "inline-flex",
|
||||
flex: "0 0 auto",
|
||||
height: 30,
|
||||
justifyContent: "center",
|
||||
p: "2px",
|
||||
width: 34,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: safeColor,
|
||||
borderRadius: "3px",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Popper
|
||||
open={pickerOpen}
|
||||
anchorEl={swatchRef.current}
|
||||
placement="bottom-start"
|
||||
style={{zIndex: 100000}}
|
||||
>
|
||||
<Box
|
||||
onMouseDown={(evt) => evt.stopPropagation()}
|
||||
onClick={(evt) => evt.stopPropagation()}
|
||||
sx={{
|
||||
backgroundColor: "background.paper",
|
||||
border: "1px solid",
|
||||
borderColor: "divider",
|
||||
borderRadius: "6px",
|
||||
boxShadow: 8,
|
||||
mt: 0.5,
|
||||
p: 1,
|
||||
"& .react-colorful": {
|
||||
width: 210,
|
||||
height: 160,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HexColorPicker color={safeColor} onChange={onChange} />
|
||||
</Box>
|
||||
</Popper>
|
||||
<HexColorInput
|
||||
color={color}
|
||||
onChange={onChange}
|
||||
prefixed={true}
|
||||
style={{
|
||||
width: inputWidth,
|
||||
minWidth: 0,
|
||||
height: "30px",
|
||||
boxSizing: "border-box",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<HexColorInput
|
||||
color={color}
|
||||
onChange={onChange}
|
||||
prefixed={true}
|
||||
style={{
|
||||
width: inputWidth,
|
||||
minWidth: 0,
|
||||
height: "30px",
|
||||
boxSizing: "border-box",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid rgba(128,128,128,0.45)",
|
||||
background: "transparent",
|
||||
color: "inherit",
|
||||
padding: "0 8px",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "0.85rem",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
border: "1px solid rgba(128,128,128,0.45)",
|
||||
background: "transparent",
|
||||
color: "inherit",
|
||||
padding: "0 8px",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "0.85rem",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</ClickAwayListener>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import MythicTextField from './MythicTextField';
|
||||
import {useQuery, gql, useMutation} from '@apollo/client';
|
||||
import { Select, Input, MenuItem, Link, IconButton } from '@mui/material';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import { Box, Select, MenuItem, Link, IconButton } from '@mui/material';
|
||||
import AceEditor from 'react-ace';
|
||||
import 'ace-builds/src-noconflict/mode-json';
|
||||
import 'ace-builds/src-noconflict/theme-monokai';
|
||||
@@ -25,11 +18,20 @@ import Chip from '@mui/material/Chip';
|
||||
import LocalOfferOutlinedIcon from '@mui/icons-material/LocalOfferOutlined';
|
||||
import {MythicStyledTooltip} from "./MythicStyledTooltip";
|
||||
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import MythicStyledTableCell from "./MythicTableCell";
|
||||
import {meState} from "../../cache";
|
||||
import { useReactiveVar } from '@apollo/client';
|
||||
import {MythicDraggableDialogTitle} from "./MythicDraggableDialogTitle";
|
||||
import {NewTagtypesDialog} from "../pages/Tags/NewTagtypesDialog";
|
||||
import {getTagReadableTextColor, TagTypeChip} from "./MythicTagChip";
|
||||
import {
|
||||
MythicDialogBody,
|
||||
MythicDialogButton,
|
||||
MythicDialogFooter,
|
||||
MythicDialogSection,
|
||||
MythicFormField,
|
||||
MythicFormGrid,
|
||||
MythicFormNote
|
||||
} from "./MythicDialogLayout";
|
||||
|
||||
const createNewTagMutationTemplate = ({target_object}) => {
|
||||
// target_object should be something like "task_id"
|
||||
@@ -121,8 +123,11 @@ export const TagsDisplay = ({tags, expand}) => {
|
||||
)
|
||||
}
|
||||
const TagChipDisplay = ({tag, expand}) => {
|
||||
const theme = useTheme();
|
||||
const [openTagDisplay, setOpenTagDisplay] = React.useState(false);
|
||||
const [label, setLabel] = React.useState(expand ? tag.tagtype.name : tag.tagtype.name[0]);
|
||||
const color = tag?.tagtype?.color || "";
|
||||
const textColor = getTagReadableTextColor(theme, color);
|
||||
const onSelectTag = (event, tag) => {
|
||||
if(event){
|
||||
event.preventDefault();
|
||||
@@ -152,9 +157,18 @@ const TagChipDisplay = ({tag, expand}) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Chip onMouseOver={onMouseOver} onMouseOut={onMouseOut} label={label} size="small" onClick={(e) => onSelectTag(e)}
|
||||
style={{float: "right", backgroundColor:tag.tagtype.color, height: "15px"}}
|
||||
sx={{
|
||||
"& .MuiChip-label": {overflow: "visible"}
|
||||
backgroundColor: color || "transparent",
|
||||
color: textColor,
|
||||
float: "right",
|
||||
height: "15px",
|
||||
"& .MuiChip-label": {
|
||||
color: "inherit",
|
||||
overflow: "visible"
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor: color || "transparent",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{openTagDisplay &&
|
||||
@@ -220,6 +234,56 @@ const StringTagDataEntry = ({name, value}) => {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
const RenderedTagDataPreview = ({data}) => {
|
||||
const trimmedData = typeof data === "string" ? data.trim() : data;
|
||||
if(trimmedData === ""){
|
||||
return <Box className="mythic-tag-data-preview-empty">No JSON data to render.</Box>;
|
||||
}
|
||||
try {
|
||||
const parsedData = typeof data === "string" ? JSON.parse(data) : data;
|
||||
if(parsedData === null || typeof parsedData !== "object"){
|
||||
return (
|
||||
<Box className="mythic-tag-data-preview-row">
|
||||
<Box className="mythic-tag-data-preview-key">value</Box>
|
||||
<Box className="mythic-tag-data-preview-value">{String(parsedData)}</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
const entries = Array.isArray(parsedData) ?
|
||||
parsedData.map((value, index) => [`[${index}]`, value]) :
|
||||
Object.entries(parsedData);
|
||||
if(entries.length === 0){
|
||||
return <Box className="mythic-tag-data-preview-empty">JSON object is empty.</Box>;
|
||||
}
|
||||
return (
|
||||
<Box className="mythic-tag-data-preview-list">
|
||||
{entries.map(([key, value]) => (
|
||||
<Box className="mythic-tag-data-preview-row" key={key}>
|
||||
<Box className="mythic-tag-data-preview-key">{key}</Box>
|
||||
<Box className="mythic-tag-data-preview-value">
|
||||
{typeof value === "string" ? (
|
||||
<StringTagDataEntry name={key} value={value} />
|
||||
) : typeof value === "boolean" ? (
|
||||
value ? "True" : "False"
|
||||
) : typeof value === "object" && value !== null ? (
|
||||
<pre>{JSON.stringify(value, null, 2)}</pre>
|
||||
) : (
|
||||
String(value)
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
} catch (error) {
|
||||
return <Box className="mythic-tag-data-preview-empty">Enter valid JSON to render a preview.</Box>;
|
||||
}
|
||||
}
|
||||
const TagReadonlyValue = ({children}) => (
|
||||
<Box className="mythic-tag-readonly-value">
|
||||
{children || "None"}
|
||||
</Box>
|
||||
)
|
||||
function ViewTagDialog(props) {
|
||||
const theme = useTheme();
|
||||
const [selectedTag, setSelectedTag] = React.useState({});
|
||||
@@ -277,118 +341,81 @@ function ViewTagDialog(props) {
|
||||
}
|
||||
props.onClose(event);
|
||||
}
|
||||
const stopClicks = (e) => {
|
||||
const stopPropagation = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
const rawData = selectedTag?.is_json ? JSON.stringify(selectedTag?.data || {}, null, 2) : selectedTag?.data || "";
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title" onClick={stopClicks}>View Tag</DialogTitle>
|
||||
<TableContainer className="mythicElement" onClick={stopClicks}>
|
||||
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<TableCell style={{width: "20%"}}>Tag Type</TableCell>
|
||||
<TableCell style={{display: "inline-flex", flexDirection: "row", width: "100%"}}>
|
||||
<Chip label={selectedTag?.tagtype?.name||""} size="small" style={{float: "right", backgroundColor:selectedTag?.tagtype?.color||""}} />
|
||||
<ViewEditTags target_object={objectInfo.object_type} target_object_id={objectInfo.object_id} me={props.me} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell>{selectedTag?.tagtype?.description || ""}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>Source</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{selectedTag?.source ||""}
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>Reference URL</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{selectedTag?.url === "" ? (
|
||||
"No reference link provided"
|
||||
) : (
|
||||
<Link href={selectedTag?.url || "#"} color="textPrimary" target="_blank" referrerPolicy='no'>{selectedTag?.url ? "click here" : "No reference link provided"}</Link>
|
||||
)}
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Data</TableCell>
|
||||
<TableCell>
|
||||
{selectedTag?.is_json ? (
|
||||
<TableContainer className="mythicElement">
|
||||
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableBody>
|
||||
{Object.keys(selectedTag.data).map( key => (
|
||||
<TableRow key={key} hover>
|
||||
<MythicStyledTableCell>{key}</MythicStyledTableCell>
|
||||
{typeof selectedTag.data[key] === "string" ? (
|
||||
<MythicStyledTableCell style={{whiteSpace: "pre-wrap"}}>
|
||||
<StringTagDataEntry name={key} value={String(selectedTag.data[key])} />
|
||||
</MythicStyledTableCell>
|
||||
) : typeof selectedTag.data[key] === "object" ? (
|
||||
Array.isArray(selectedTag.data[key]) ? (
|
||||
<TableCell style={{whiteSpace: "pre-wrap"}}>{JSON.stringify(selectedTag.data[key], null, 2)}</TableCell>
|
||||
) : (
|
||||
<MythicStyledTableCell>
|
||||
<Table size={"small"} >
|
||||
<TableBody>
|
||||
{Object.keys(selectedTag.data[key]).map(key2 => (
|
||||
<TableRow key={key2} >
|
||||
<MythicStyledTableCell>{key2}</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<StringTagDataEntry name={key2} value={String(selectedTag.data[key][key2])} />
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
</Table>
|
||||
</MythicStyledTableCell>
|
||||
)
|
||||
) : typeof selectedTag.data[key] === "boolean" ? (
|
||||
<MythicStyledTableCell>{selectedTag.data[key] ? "True" : "False"}</MythicStyledTableCell>
|
||||
) :
|
||||
(
|
||||
<MythicStyledTableCell>{String(selectedTag.data[key])}</MythicStyledTableCell>
|
||||
)
|
||||
}
|
||||
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
) : (
|
||||
<AceEditor
|
||||
mode="json"
|
||||
theme={theme.palette.mode === "dark" ? "monokai" : "xcode"}
|
||||
fontSize={14}
|
||||
showGutter={true}
|
||||
maxLines={20}
|
||||
highlightActiveLine={false}
|
||||
value={selectedTag?.data || ""}
|
||||
width={"100%"}
|
||||
setOptions={{
|
||||
showLineNumbers: true,
|
||||
tabSize: 4,
|
||||
useWorker: false,
|
||||
wrapBehavioursEnabled: true,
|
||||
wrap: true
|
||||
}}/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<DialogActions onClick={(e) => e.stopPropagation()}>
|
||||
<Button onClick={onClose} variant="contained" color="primary">
|
||||
<MythicDraggableDialogTitle>View Tag</MythicDraggableDialogTitle>
|
||||
<DialogContent dividers={true} style={{width: "100%"}} onClick={stopPropagation}>
|
||||
<MythicDialogBody>
|
||||
<MythicDialogSection
|
||||
title="Tag Type"
|
||||
description={selectedTag?.tagtype?.description || "No description provided."}
|
||||
actions={
|
||||
objectInfo.object_type !== "" &&
|
||||
<ViewEditTags target_object={objectInfo.object_type} target_object_id={objectInfo.object_id} me={props.me} />
|
||||
}
|
||||
>
|
||||
{selectedTag?.tagtype &&
|
||||
<TagTypeChip tagtype={selectedTag.tagtype} />
|
||||
}
|
||||
</MythicDialogSection>
|
||||
<MythicDialogSection title="Reference Details">
|
||||
<MythicFormGrid minWidth="16rem">
|
||||
<MythicFormField label="Source">
|
||||
<TagReadonlyValue>{selectedTag?.source || "None"}</TagReadonlyValue>
|
||||
</MythicFormField>
|
||||
<MythicFormField label="External URL">
|
||||
<TagReadonlyValue>
|
||||
{selectedTag?.url ? (
|
||||
<Link href={selectedTag.url} color="textPrimary" target="_blank" referrerPolicy='no'>
|
||||
{selectedTag.url}
|
||||
</Link>
|
||||
) : (
|
||||
"No reference link provided"
|
||||
)}
|
||||
</TagReadonlyValue>
|
||||
</MythicFormField>
|
||||
</MythicFormGrid>
|
||||
</MythicDialogSection>
|
||||
<MythicDialogSection title="Tag Data">
|
||||
{selectedTag?.is_json ? (
|
||||
<Box className="mythic-tag-data-preview-frame mythic-tag-data-preview-frame-full">
|
||||
<RenderedTagDataPreview data={selectedTag?.data || {}} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box className="mythic-tag-editor-frame">
|
||||
<AceEditor
|
||||
mode="json"
|
||||
theme={theme.palette.mode === "dark" ? "monokai" : "xcode"}
|
||||
fontSize={14}
|
||||
showGutter={true}
|
||||
showPrintMargin={false}
|
||||
minLines={8}
|
||||
maxLines={18}
|
||||
highlightActiveLine={false}
|
||||
readOnly={true}
|
||||
value={rawData}
|
||||
width={"100%"}
|
||||
setOptions={{
|
||||
showLineNumbers: true,
|
||||
tabSize: 4,
|
||||
useWorker: false,
|
||||
wrapBehavioursEnabled: true,
|
||||
wrap: true
|
||||
}}/>
|
||||
</Box>
|
||||
)}
|
||||
</MythicDialogSection>
|
||||
</MythicDialogBody>
|
||||
</DialogContent>
|
||||
<MythicDialogFooter onClick={stopPropagation}>
|
||||
<MythicDialogButton onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</MythicDialogButton>
|
||||
</MythicDialogFooter>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -486,8 +513,15 @@ const handleTaskTypeChange = (evt) => {
|
||||
setNewData(String(evt.target.value.data));
|
||||
}
|
||||
}
|
||||
const handleNewTagCreate = ({tagtype_id, source, url, data, id}) => {
|
||||
props.onClose();
|
||||
const handleNewTagCreate = ({tagtype_id, source, url, data, id, tagtype}) => {
|
||||
const newTag = {id, source, url, data, tagtype_id, tagtype};
|
||||
const newTags = [...existingTags, newTag].sort((a, b) => a.tagtype.name.localeCompare(b.tagtype.name));
|
||||
setExistingTags(newTags);
|
||||
setSelectedTag(newTag);
|
||||
setNewSource(source);
|
||||
setNewURL(url);
|
||||
setNewData(data);
|
||||
setOpenNewDialog(false);
|
||||
}
|
||||
const onAcceptDelete = () => {
|
||||
deleteTag({variables: {tag_id: selectedTag.id}});
|
||||
@@ -506,88 +540,117 @@ return (
|
||||
onClose={()=>{setOpenNewDialog(false);}}
|
||||
onSubmit={handleNewTagCreate} />}
|
||||
/>}
|
||||
<Table size="small" style={{ width: "100%", overflow: "scroll"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell style={{width: "30%"}}>Select Existing Tag to Edit or Add New</MythicStyledTableCell>
|
||||
<MythicStyledTableCell style={{display: "inline-flex", flexDirection: "row-reverse"}}>
|
||||
<MythicStyledTooltip title={"Add New Tag"}>
|
||||
<IconButton variant='contained' color="success" style={{float: "right"}} onClick={() => setOpenNewDialog(true)} >
|
||||
<AddCircleOutlineIcon />
|
||||
<MythicDialogBody>
|
||||
<MythicDialogSection
|
||||
title="Tag Selection"
|
||||
description={existingTags.length === 0 ? "No tags are attached yet." : "Choose the tag you want to update."}
|
||||
actions={
|
||||
<MythicStyledTooltip title={"Add New Tag"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-success" size="small" onClick={() => setOpenNewDialog(true)}>
|
||||
<AddCircleOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
>
|
||||
{existingTags.length === 0 ? (
|
||||
<MythicFormNote>
|
||||
Add a tag to capture source, reference, and structured data for this item.
|
||||
</MythicFormNote>
|
||||
) : (
|
||||
<Box sx={{display: "grid", gap: 1, gridTemplateColumns: {xs: "1fr", md: selectedTag.id ? "minmax(0, 1fr) auto" : "1fr"}, alignItems: "end"}}>
|
||||
<MythicFormField label="Existing Tag">
|
||||
<Select
|
||||
fullWidth
|
||||
size="small"
|
||||
value={selectedTag}
|
||||
onChange={handleTaskTypeChange}
|
||||
renderValue={(tag) => tag?.id ? <TagTypeChip tagtype={tag.tagtype} /> : "Select a tag"}
|
||||
>
|
||||
{existingTags.map( (opt) => (
|
||||
<MenuItem value={opt} key={opt.id}>
|
||||
<TagTypeChip tagtype={opt.tagtype} />
|
||||
</MenuItem>
|
||||
) )}
|
||||
</Select>
|
||||
</MythicFormField>
|
||||
{selectedTag.id &&
|
||||
<MythicStyledTooltip title={"Delete Tag"}>
|
||||
<Box sx={{alignItems: "center", alignSelf: "end", display: "flex", height: 38}}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-danger" size="small" onClick={()=>{setOpenDeleteDialog(true);}}>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<Select
|
||||
labelId="demo-dialog-select-label"
|
||||
id="demo-dialog-select"
|
||||
value={selectedTag}
|
||||
onChange={handleTaskTypeChange}
|
||||
input={<Input />}
|
||||
>
|
||||
{existingTags.map( (opt) => (
|
||||
<MenuItem value={opt} key={opt.id}>
|
||||
<Chip label={opt.tagtype.name} size="small" style={{float: "right", backgroundColor:opt.tagtype.color}} />
|
||||
</MenuItem>
|
||||
) )}
|
||||
</Select>
|
||||
{selectedTag.id &&
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-danger" size="small" style={{float: "right"}} onClick={()=>{setOpenDeleteDialog(true);}}><DeleteIcon fontSize="small" /></IconButton>
|
||||
</Box>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
{selectedTag.id && selectedTag?.tagtype?.description &&
|
||||
<Box sx={{gridColumn: "1 / -1"}}>
|
||||
<MythicFormNote>{selectedTag.tagtype.description}</MythicFormNote>
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
)}
|
||||
{openDelete &&
|
||||
<MythicConfirmDialog onClose={() => {setOpenDeleteDialog(false);}} onSubmit={onAcceptDelete} open={openDelete}/>
|
||||
}
|
||||
</MythicDialogSection>
|
||||
{selectedTag.id &&
|
||||
<>
|
||||
<MythicDialogSection title="Reference Details">
|
||||
<MythicFormGrid minWidth="16rem">
|
||||
<MythicFormField label="Source">
|
||||
<MythicTextField value={newSource} onChange={onChangeSource} name="Source of tag data" showLabel={false} marginTop="0" marginBottom="0" />
|
||||
</MythicFormField>
|
||||
<MythicFormField label="External URL">
|
||||
<MythicTextField value={newURL} onChange={onChangeURL} name="External URL reference" showLabel={false} marginTop="0" marginBottom="0" />
|
||||
{newURL &&
|
||||
<Link href={newURL} color="textPrimary" target="_blank" referrerPolicy='no' sx={{display: "inline-flex", mt: 0.5, fontSize: "0.78rem"}}>
|
||||
Open reference
|
||||
</Link>
|
||||
}
|
||||
{openDelete &&
|
||||
<MythicConfirmDialog onClose={() => {setOpenDeleteDialog(false);}} onSubmit={onAcceptDelete} open={openDelete}/>
|
||||
}
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>Tag Description</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>{selectedTag?.tagtype?.description || ""}</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>Source</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<MythicTextField value={newSource} onChange={onChangeSource} name="Source of tag data" />
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>External URL</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<MythicTextField value={newURL} onChange={onChangeURL} name="External URL reference" />
|
||||
<Link href={newURL} color="textPrimary" target="_blank" referrerPolicy='no'>{newURL ? "click here" : ""}</Link>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>JSON Data</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<AceEditor
|
||||
mode="json"
|
||||
theme={theme.palette.mode === "dark" ? "monokai" : "xcode"}
|
||||
onChange={onEditorChange}
|
||||
fontSize={14}
|
||||
showGutter={true}
|
||||
maxLines={20}
|
||||
highlightActiveLine={true}
|
||||
value={newData}
|
||||
width={"100%"}
|
||||
setOptions={{
|
||||
showLineNumbers: true,
|
||||
tabSize: 4,
|
||||
useWorker: false
|
||||
}}/>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</MythicFormField>
|
||||
</MythicFormGrid>
|
||||
</MythicDialogSection>
|
||||
<MythicDialogSection title="JSON Data">
|
||||
<Box className="mythic-tag-data-split">
|
||||
<Box className="mythic-tag-editor-frame">
|
||||
<AceEditor
|
||||
mode="json"
|
||||
theme={theme.palette.mode === "dark" ? "monokai" : "xcode"}
|
||||
onChange={onEditorChange}
|
||||
fontSize={14}
|
||||
showGutter={true}
|
||||
showPrintMargin={false}
|
||||
minLines={10}
|
||||
maxLines={18}
|
||||
highlightActiveLine={true}
|
||||
value={newData}
|
||||
width={"100%"}
|
||||
setOptions={{
|
||||
showLineNumbers: true,
|
||||
tabSize: 4,
|
||||
useWorker: false,
|
||||
wrap: true
|
||||
}}/>
|
||||
</Box>
|
||||
<Box className="mythic-tag-data-preview-frame">
|
||||
<RenderedTagDataPreview data={newData} />
|
||||
</Box>
|
||||
</Box>
|
||||
</MythicDialogSection>
|
||||
</>
|
||||
}
|
||||
</MythicDialogBody>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onClose} variant="contained" color="primary">
|
||||
<MythicDialogFooter>
|
||||
<MythicDialogButton onClick={props.onClose}>
|
||||
Close
|
||||
</Button>
|
||||
</MythicDialogButton>
|
||||
{selectedTag.id &&
|
||||
<Button onClick={onSubmit} variant="contained" color="success">
|
||||
<MythicDialogButton intent="primary" onClick={onSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</MythicDialogButton>
|
||||
}
|
||||
|
||||
</DialogActions>
|
||||
</MythicDialogFooter>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -598,7 +661,8 @@ export function NewTagDialog(props) {
|
||||
const [newData, setNewData] = React.useState("");
|
||||
const [selectedTagType, setSelectedTagType] = React.useState("");
|
||||
const [existingTagTypes, setExistingTagTypes] = React.useState([]);
|
||||
const { loading, error } = useQuery(getTagtypesQuery, {
|
||||
const [openNewTagTypeDialog, setOpenNewTagTypeDialog] = React.useState(false);
|
||||
useQuery(getTagtypesQuery, {
|
||||
onCompleted: data => {
|
||||
setExistingTagTypes(data.tagtype);
|
||||
if(data.tagtype.length > 0){
|
||||
@@ -611,7 +675,7 @@ export function NewTagDialog(props) {
|
||||
onCompleted: data => {
|
||||
if(data.createTag.status === "success"){
|
||||
snackActions.success("Successfully created new tag!");
|
||||
props.onSubmit({source:newSource, url:newURL, data:newData, tagtype_id:selectedTagType.id, id: data.createTag.id});
|
||||
props.onSubmit({source:newSource, url:newURL, data:newData, tagtype_id:selectedTagType.id, id: data.createTag.id, tagtype: selectedTagType});
|
||||
props.onClose()
|
||||
} else {
|
||||
snackActions.error(data.createTag.error);
|
||||
@@ -625,6 +689,11 @@ export function NewTagDialog(props) {
|
||||
const handleTaskTypeChange = (evt) => {
|
||||
setSelectedTagType(evt.target.value);
|
||||
}
|
||||
const onNewTagType = ({name, description, id, color}) => {
|
||||
const newTagType = {name, description, id, color};
|
||||
setExistingTagTypes((prev) => [...prev, newTagType].sort((a, b) => a.name.localeCompare(b.name)));
|
||||
setSelectedTagType(newTagType);
|
||||
}
|
||||
const onSubmit = () => {
|
||||
newTag({variables:
|
||||
{source:newSource, url:newURL, data:newData,
|
||||
@@ -644,87 +713,103 @@ export function NewTagDialog(props) {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{openNewTagTypeDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openNewTagTypeDialog}
|
||||
onClose={()=>{setOpenNewTagTypeDialog(false);}}
|
||||
innerDialog={
|
||||
<NewTagtypesDialog
|
||||
onClose={()=>{setOpenNewTagTypeDialog(false);}}
|
||||
onSubmit={onNewTagType}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
<MythicDraggableDialogTitle >Add New Tag</MythicDraggableDialogTitle>
|
||||
<DialogContent dividers={true} style={{width: "100%"}}>
|
||||
<Table size="small" style={{ overflow: "scroll", width: "100%"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell style={{width: "20%"}}>
|
||||
<Typography>
|
||||
Tag
|
||||
</Typography>
|
||||
<Typography size="small" component="span" style={{fontSize: theme.typography.pxToRem(15)}}>
|
||||
To create a new tag type click <Link style={{wordBreak: "break-all"}}
|
||||
color="textPrimary"
|
||||
href={"/new/tagtypes"}
|
||||
underline="always" target="_blank">
|
||||
here
|
||||
</Link>
|
||||
</Typography>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Select
|
||||
labelId="demo-dialog-select-label"
|
||||
id="demo-dialog-select"
|
||||
value={selectedTagType}
|
||||
onChange={handleTaskTypeChange}
|
||||
input={<Input style={{width: "100%"}}/>}
|
||||
>
|
||||
{existingTagTypes.map( (opt) => (
|
||||
<MenuItem value={opt} key={opt.name}>
|
||||
<Chip label={opt.name} size="small" style={{ backgroundColor:opt.color}} />
|
||||
</MenuItem>
|
||||
) )}
|
||||
</Select>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>Source</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<MythicTextField value={newSource} onChange={onChangeSource} name="Source of tag data" />
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>External URL</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<MythicTextField value={newURL} onChange={onChangeURL} name="External URL reference" />
|
||||
<Link href={newURL} color="textPrimary" target="_blank" referrerPolicy='no'>{newURL}</Link>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>JSON Data</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<AceEditor
|
||||
<MythicDialogBody>
|
||||
<MythicDialogSection
|
||||
title="Tag Type"
|
||||
description="Select the taxonomy entry this tag should use."
|
||||
actions={
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-info" size="small" variant="outlined" onClick={() => setOpenNewTagTypeDialog(true)}>
|
||||
Manage Tag Types
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<MythicFormField label="Tag">
|
||||
<Select
|
||||
fullWidth
|
||||
size="small"
|
||||
value={selectedTagType}
|
||||
onChange={handleTaskTypeChange}
|
||||
displayEmpty
|
||||
renderValue={(tagtype) => tagtype?.id ? <TagTypeChip tagtype={tagtype} /> : "No tag types available"}
|
||||
>
|
||||
{existingTagTypes.map( (opt) => (
|
||||
<MenuItem value={opt} key={opt.name}>
|
||||
<TagTypeChip tagtype={opt} />
|
||||
</MenuItem>
|
||||
) )}
|
||||
</Select>
|
||||
</MythicFormField>
|
||||
{selectedTagType?.description &&
|
||||
<MythicFormNote>{selectedTagType.description}</MythicFormNote>
|
||||
}
|
||||
</MythicDialogSection>
|
||||
<MythicDialogSection title="Reference Details">
|
||||
<MythicFormGrid minWidth="16rem">
|
||||
<MythicFormField label="Source">
|
||||
<MythicTextField value={newSource} onChange={onChangeSource} name="Source of tag data" showLabel={false} marginTop="0" marginBottom="0" />
|
||||
</MythicFormField>
|
||||
<MythicFormField label="External URL">
|
||||
<MythicTextField value={newURL} onChange={onChangeURL} name="External URL reference" showLabel={false} marginTop="0" marginBottom="0" />
|
||||
{newURL &&
|
||||
<Link href={newURL} color="textPrimary" target="_blank" referrerPolicy='no' sx={{display: "inline-flex", mt: 0.5, fontSize: "0.78rem"}}>
|
||||
Open reference
|
||||
</Link>
|
||||
}
|
||||
</MythicFormField>
|
||||
</MythicFormGrid>
|
||||
</MythicDialogSection>
|
||||
<MythicDialogSection title="JSON Data">
|
||||
<Box className="mythic-tag-data-split">
|
||||
<Box className="mythic-tag-editor-frame">
|
||||
<AceEditor
|
||||
mode="json"
|
||||
theme={theme.palette.mode === "dark" ? "monokai" : "xcode"}
|
||||
onChange={onEditorChange}
|
||||
fontSize={14}
|
||||
showGutter={true}
|
||||
maxLines={20}
|
||||
showPrintMargin={false}
|
||||
minLines={10}
|
||||
maxLines={18}
|
||||
highlightActiveLine={true}
|
||||
value={newData}
|
||||
width={"100%"}
|
||||
setOptions={{
|
||||
showLineNumbers: true,
|
||||
tabSize: 4,
|
||||
useWorker: false
|
||||
useWorker: false,
|
||||
wrap: true
|
||||
}}/>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
<Box className="mythic-tag-data-preview-frame">
|
||||
<RenderedTagDataPreview data={newData} />
|
||||
</Box>
|
||||
</Box>
|
||||
</MythicDialogSection>
|
||||
</MythicDialogBody>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onClose} variant="contained" color="primary">
|
||||
<MythicDialogFooter>
|
||||
<MythicDialogButton onClick={props.onClose}>
|
||||
Close
|
||||
</Button>
|
||||
</MythicDialogButton>
|
||||
{selectedTagType !== "" &&
|
||||
<Button onClick={onSubmit} variant="contained" color="success">
|
||||
<MythicDialogButton intent="primary" onClick={onSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</MythicDialogButton>
|
||||
}
|
||||
|
||||
</DialogActions>
|
||||
</MythicDialogFooter>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
|
||||
export const getTagReadableTextColor = (theme, color) => {
|
||||
if(!color){
|
||||
return theme.palette.text.primary;
|
||||
}
|
||||
try {
|
||||
return theme.palette.getContrastText(color);
|
||||
} catch (error) {
|
||||
return theme.palette.text.primary;
|
||||
}
|
||||
}
|
||||
|
||||
export const TagTypeChip = ({tagtype, label, sx={}, ...props}) => {
|
||||
const theme = useTheme();
|
||||
const color = tagtype?.color || "";
|
||||
const textColor = getTagReadableTextColor(theme, color);
|
||||
return (
|
||||
<Chip
|
||||
label={label || tagtype?.name || "Tag"}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: color || "transparent",
|
||||
border: "1px solid",
|
||||
borderColor: color ? "rgba(0,0,0,0.16)" : theme.table?.borderSoft || theme.borderColor,
|
||||
color: textColor,
|
||||
fontWeight: 800,
|
||||
maxWidth: "100%",
|
||||
"& .MuiChip-label": {
|
||||
color: "inherit",
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
...sx,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -56,6 +56,7 @@ export const classes = {
|
||||
taskCommandTextCompact: `${PREFIX}-taskCommandTextCompact`,
|
||||
taskCommandName: `${PREFIX}-taskCommandName`,
|
||||
taskCommandParams: `${PREFIX}-taskCommandParams`,
|
||||
taskTags: `${PREFIX}-taskTags`,
|
||||
taskChildToggle: `${PREFIX}-taskChildToggle`,
|
||||
taskCommentBlock: `${PREFIX}-taskCommentBlock`,
|
||||
consolePrompt: `${PREFIX}-consolePrompt`
|
||||
@@ -231,6 +232,9 @@ export const StyledPaper = styled(Paper)((
|
||||
minWidth: 0,
|
||||
width: "100%",
|
||||
},
|
||||
[`& .${classes.taskHeaderBodyCompact} .${classes.taskCommandRow}`]: {
|
||||
alignItems: "center",
|
||||
},
|
||||
[`& .${classes.taskCommandText}`]: {
|
||||
color: theme.taskPromptCommandTextColor,
|
||||
flex: "1 1 auto",
|
||||
@@ -252,6 +256,33 @@ export const StyledPaper = styled(Paper)((
|
||||
color: theme.palette.text.primary,
|
||||
overflowWrap: "anywhere",
|
||||
},
|
||||
[`& .${classes.taskTags}`]: {
|
||||
alignItems: "center",
|
||||
display: "inline-flex",
|
||||
flex: "0 1 auto",
|
||||
flexWrap: "wrap",
|
||||
gap: 3,
|
||||
justifyContent: "flex-end",
|
||||
marginLeft: "auto",
|
||||
maxWidth: "34%",
|
||||
minWidth: 0,
|
||||
"& .MuiChip-root": {
|
||||
float: "none !important",
|
||||
height: "16px !important",
|
||||
margin: 0,
|
||||
maxWidth: "10rem",
|
||||
},
|
||||
"& .MuiChip-label": {
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
},
|
||||
[`& .${classes.taskHeaderBodyCompact} .${classes.taskTags}`]: {
|
||||
flexWrap: "nowrap",
|
||||
maxHeight: 20,
|
||||
overflow: "hidden",
|
||||
},
|
||||
[`& .${classes.taskChildToggle}`]: {
|
||||
color: theme.palette.text.secondary,
|
||||
height: 24,
|
||||
@@ -494,8 +525,13 @@ const TaskHeaderAction = ({title, children, ...props}) => (
|
||||
</MythicStyledTooltip>
|
||||
)
|
||||
const TaskTagDisplay = ({task}) => {
|
||||
if(!task.tags || task.tags.length === 0){
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<TagsDisplay tags={task.tags} />
|
||||
<span className={classes.taskTags}>
|
||||
<TagsDisplay tags={task.tags} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
const isTaskHeaderInteractiveClick = (e) => {
|
||||
@@ -628,6 +664,7 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
|
||||
<MythicStyledTooltip maxWidth={"calc(80vw)"}
|
||||
enterDelay={2000}
|
||||
placement={"top"}
|
||||
tooltipStyle={{display: "block", flex: "1 1 auto", minWidth: 0}}
|
||||
title={commandLine} >
|
||||
<Typography className={compact ? `${classes.taskCommandText} ${classes.taskCommandTextCompact}` : classes.taskCommandText} component="div">
|
||||
<span className={classes.taskCommandName}>{command}</span>
|
||||
@@ -636,8 +673,8 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
|
||||
}
|
||||
</Typography>
|
||||
</MythicStyledTooltip>
|
||||
<TaskTagDisplay task={task}/>
|
||||
</div>
|
||||
<TaskTagDisplay task={task}/>
|
||||
</div>
|
||||
{openKillTaskButton.open &&
|
||||
<TaskFromUIButton ui_feature={"task:job_kill"}
|
||||
|
||||
@@ -26,7 +26,6 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
|
||||
import { faExternalLinkAlt, faExpandArrowsAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import Button from '@mui/material/Button';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
@@ -69,6 +68,13 @@ query subResponsesQuery($task_id: Int!) {
|
||||
}
|
||||
}`;
|
||||
|
||||
const responseActionClass = (tone = "info") => {
|
||||
return `mythic-response-action-button mythic-response-action-hover-${tone}`;
|
||||
};
|
||||
const responseMenuItemClass = (tone = "info") => {
|
||||
return `mythic-response-action-menu-item mythic-response-action-hover-${tone}`;
|
||||
};
|
||||
|
||||
export const TaskDisplayContainer = ({task, me}) => {
|
||||
const [viewBrowserScript, setViewBrowserScript] = React.useState(true);
|
||||
const [commandID, setCommandID] = React.useState(0);
|
||||
@@ -193,7 +199,6 @@ export const TaskDisplayContainerConsole = ({task, me}) => {
|
||||
const SideDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput,
|
||||
toggleOpenSearch, taskData, viewAllOutput, me,
|
||||
responseRef, viewBrowserScript}) => {
|
||||
const theme = useTheme();
|
||||
const [task, setTask] = React.useState(taskData || {});
|
||||
const eventingDataRef = React.useRef({name: "", value: 0});
|
||||
const [openEventingDialog, setOpenEventingDialog] = React.useState(false);
|
||||
@@ -338,78 +343,78 @@ const SideDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput,
|
||||
}
|
||||
/>
|
||||
}
|
||||
<Paper elevation={5} style={{width: "30px", display: "flex", flexDirection: "column", alignItems: "center", overflow: "hidden",
|
||||
<Paper className="mythic-response-side-actions" elevation={5} style={{width: "30px", display: "flex", flexDirection: "column", alignItems: "center", overflow: "hidden",
|
||||
backgroundColor: "transparent", height: "100%"}}>
|
||||
<MythicStyledTooltip title={"Toggle BrowserScript"} >
|
||||
<IconButton onClick={toggleViewBrowserScript} style={{paddingBottom: "3px"}}>
|
||||
{viewBrowserScript ? <CodeOffIcon color={"error"} /> : <CodeIcon color={"success"}/>}
|
||||
<IconButton className={responseActionClass(viewBrowserScript ? "danger" : "success")} onClick={toggleViewBrowserScript} size="small">
|
||||
{viewBrowserScript ? <CodeOffIcon fontSize="small" /> : <CodeIcon fontSize="small" />}
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={viewAllOutput ? "View Paginated Output" : "View All Output"}>
|
||||
<IconButton onClick={toggleSelectAllOutput} style={{paddingBottom: "3px"}}>
|
||||
{viewAllOutput ? <CloseFullscreenIcon color={"error"} /> : <FontAwesomeIcon style={{color: theme.palette.success.main}} icon={faExpandArrowsAlt} size="sm" />}
|
||||
<IconButton className={responseActionClass(viewAllOutput ? "danger" : "success")} onClick={toggleSelectAllOutput} size="small">
|
||||
{viewAllOutput ? <CloseFullscreenIcon fontSize="small" /> : <FontAwesomeIcon icon={faExpandArrowsAlt} size="sm" />}
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Search Output"}>
|
||||
<IconButton onClick={toggleOpenSearch} style={{paddingBottom: "3px"}}>
|
||||
<SearchIcon color={"info"} />
|
||||
<IconButton className={responseActionClass("info")} onClick={toggleOpenSearch} size="small">
|
||||
<SearchIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Download output"}>
|
||||
<IconButton onClick={onDownloadResponses} style={{paddingBottom: "3px"}}>
|
||||
<GetAppIcon color={"success"}/>
|
||||
<IconButton className={responseActionClass("success")} onClick={onDownloadResponses} size="small">
|
||||
<GetAppIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Download screenshot of output"}>
|
||||
<IconButton onClick={onDownloadImageClickPng} style={{paddingBottom: "3px"}}>
|
||||
<InsertPhotoIcon/>
|
||||
<IconButton className={responseActionClass("success")} onClick={onDownloadImageClickPng} size="small">
|
||||
<InsertPhotoIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Edit Tags"}>
|
||||
<IconButton onClick={()=>{setOpenTaskTagDialog(true)}} style={{paddingBottom: "3px"}}>
|
||||
<LocalOfferOutlinedIcon/>
|
||||
<IconButton className={responseActionClass("info")} onClick={()=>{setOpenTaskTagDialog(true)}} size="small">
|
||||
<LocalOfferOutlinedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Open Task in New Window"}>
|
||||
<IconButton onClick={()=> {window.open('/new/task/' + task.display_id, "_blank")}} style={{paddingBottom: "3px"}}>
|
||||
<IconButton className={responseActionClass("info")} onClick={()=> {window.open('/new/task/' + task.display_id, "_blank")}} size="small">
|
||||
<FontAwesomeIcon icon={faExternalLinkAlt} size="sm" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Copy original params to clipboard"}>
|
||||
<IconButton onClick={copyToClipboard} style={{paddingBottom: "3px"}}>
|
||||
<FileCopyOutlinedIcon/>
|
||||
<IconButton className={responseActionClass("info")} onClick={copyToClipboard} size="small">
|
||||
<FileCopyOutlinedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Edit Comment"}>
|
||||
<IconButton onClick={()=>{setOpenCommentDialog(true)}} style={{paddingBottom: "3px"}}>
|
||||
<RateReviewOutlinedIcon/>
|
||||
<IconButton className={responseActionClass("info")} onClick={()=>{setOpenCommentDialog(true)}} size="small">
|
||||
<RateReviewOutlinedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"View All Parameters And Timestamps"}>
|
||||
<IconButton onClick={()=>{setOpenParametersDialog(true);}} style={{paddingBottom: "3px"}}>
|
||||
<KeyboardIcon/>
|
||||
<IconButton className={responseActionClass("info")} onClick={()=>{setOpenParametersDialog(true);}} size="small">
|
||||
<KeyboardIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"View Stdout/Stderr of Task"}>
|
||||
<IconButton onClick={()=>{setOpenStdoutStderrDialog(true);}} style={{paddingBottom: "3px"}}>
|
||||
<FontAwesomeIcon style={{color: theme.palette.error.main}} icon={faExclamationTriangle} size="sm" />
|
||||
<IconButton className={responseActionClass("danger")} onClick={()=>{setOpenStdoutStderrDialog(true);}} size="small">
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} size="sm" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Trigger Eventing Based on Task"}>
|
||||
<IconButton onClick={()=>{onTriggerEventing();}} style={{paddingBottom: "3px"}}>
|
||||
<PlayCircleFilledTwoToneIcon />
|
||||
<IconButton className={responseActionClass("info")} onClick={()=>{onTriggerEventing();}} size="small">
|
||||
<PlayCircleFilledTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
{task.opsec_pre_blocked === null ? null : ( task.opsec_pre_bypassed === false ? (
|
||||
<MythicStyledTooltip title={"Submit OPSEC PreCheck Bypass Request"}>
|
||||
<IconButton onClick={()=>{setOpenOpsecDialog({open: true, view: "pre"})}} style={{paddingBottom: "3px"}}>
|
||||
<LockIcon style={{color: theme.palette.error.main}}/>
|
||||
<IconButton className={responseActionClass("danger")} onClick={()=>{setOpenOpsecDialog({open: true, view: "pre"})}} size="small">
|
||||
<LockIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
): (
|
||||
<MythicStyledTooltip title={"View OPSEC PreCheck Data"}>
|
||||
<IconButton onClick={()=>{setOpenOpsecDialog({open: true, view: "pre"})}} style={{paddingBottom: "3px"}}>
|
||||
<LockOpenIcon style={{color: theme.palette.success.main}}/>
|
||||
<IconButton className={responseActionClass("success")} onClick={()=>{setOpenOpsecDialog({open: true, view: "pre"})}} size="small">
|
||||
<LockOpenIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)
|
||||
@@ -417,14 +422,14 @@ const SideDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput,
|
||||
}
|
||||
{task.opsec_post_blocked === null ? null : ( task.opsec_post_bypassed === false ? (
|
||||
<MythicStyledTooltip title={"Submit OPSEC PostCheck Bypass Request"}>
|
||||
<IconButton onClick={()=>{setOpenOpsecDialog({open: true, view: "post"})}} style={{paddingBottom: "3px"}}>
|
||||
<LockIcon style={{color: theme.palette.error.main}}/>
|
||||
<IconButton className={responseActionClass("danger")} onClick={()=>{setOpenOpsecDialog({open: true, view: "post"})}} size="small">
|
||||
<LockIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
): (
|
||||
<MythicStyledTooltip title={"View OPSEC PostCheck Data"}>
|
||||
<IconButton onClick={()=>{setOpenOpsecDialog({open: true, view: "post"})}} style={{paddingBottom: "3px"}}>
|
||||
<LockOpenIcon style={{color: theme.palette.success.main}}/>
|
||||
<IconButton className={responseActionClass("success")} onClick={()=>{setOpenOpsecDialog({open: true, view: "post"})}} size="small">
|
||||
<LockOpenIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)
|
||||
@@ -432,22 +437,22 @@ const SideDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput,
|
||||
}
|
||||
{task.token === null ? null : (
|
||||
<MythicStyledTooltip title={"View Token Information"}>
|
||||
<IconButton onClick={()=>{setOpenTokenDialog(true)}} style={{paddingBottom: "3px"}}>
|
||||
<ConfirmationNumberIcon />
|
||||
<IconButton className={responseActionClass("info")} onClick={()=>{setOpenTokenDialog(true)}} size="small">
|
||||
<ConfirmationNumberIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)}
|
||||
{task.status.toLowerCase().includes("error: container") &&
|
||||
<MythicStyledTooltip title={"Resubmit Tasking"}>
|
||||
<IconButton onClick={onReissueTask} style={{paddingBottom: "3px"}}>
|
||||
<ReplayIcon style={{color: theme.palette.warning.main}}/>
|
||||
<IconButton className={responseActionClass("warning")} onClick={onReissueTask} size="small">
|
||||
<ReplayIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
{task.status.toLowerCase().includes("error: task") &&
|
||||
<MythicStyledTooltip title={"Resubmit Task Handler"}>
|
||||
<IconButton onClick={onReissueTaskHandler} style={{paddingBottom: "3px"}}>
|
||||
<ReplayIcon style={{color: theme.palette.warning.main}}/>
|
||||
<IconButton className={responseActionClass("warning")} onClick={onReissueTaskHandler} size="small">
|
||||
<ReplayIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
@@ -616,7 +621,7 @@ const TaskActionsToolbarGeneric = ({toggleViewBrowserScript, toggleSelectAllOutp
|
||||
}
|
||||
/>
|
||||
}
|
||||
<Paper elevation={0} sx={{
|
||||
<Paper className="mythic-response-actions-toolbar" elevation={0} sx={{
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.palette.mode === "dark" ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)",
|
||||
borderLeft: 0,
|
||||
@@ -631,63 +636,66 @@ const TaskActionsToolbarGeneric = ({toggleViewBrowserScript, toggleSelectAllOutp
|
||||
py: 0.5,
|
||||
}}>
|
||||
<MythicStyledTooltip title={"Toggle BrowserScript"}>
|
||||
<IconButton size="small" onClick={toggleViewBrowserScript}>
|
||||
{viewBrowserScript ? <CodeOffIcon color={"error"} fontSize="small" /> : <CodeIcon color={"success"} fontSize="small" />}
|
||||
<IconButton className={responseActionClass(viewBrowserScript ? "danger" : "success")} size="small" onClick={toggleViewBrowserScript}>
|
||||
{viewBrowserScript ? <CodeOffIcon fontSize="small" /> : <CodeIcon fontSize="small" />}
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={viewAllOutput ? "View Paginated Output" : "View All Output"}>
|
||||
<IconButton size="small" onClick={toggleSelectAllOutput}>
|
||||
{viewAllOutput ? <CloseFullscreenIcon color={"error"} fontSize="small" /> : <FontAwesomeIcon style={{color: theme.palette.success.main}} icon={faExpandArrowsAlt} size="sm" />}
|
||||
<IconButton className={responseActionClass(viewAllOutput ? "danger" : "success")} size="small" onClick={toggleSelectAllOutput}>
|
||||
{viewAllOutput ? <CloseFullscreenIcon fontSize="small" /> : <FontAwesomeIcon icon={faExpandArrowsAlt} size="sm" />}
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Search Output"}>
|
||||
<IconButton size="small" onClick={toggleOpenSearch}>
|
||||
<SearchIcon color={"info"} fontSize="small" />
|
||||
<IconButton className={responseActionClass("info")} size="small" onClick={toggleOpenSearch}>
|
||||
<SearchIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Download output"}>
|
||||
<IconButton size="small" onClick={onDownloadResponses}>
|
||||
<GetAppIcon color={"success"} fontSize="small" />
|
||||
<IconButton className={responseActionClass("success")} size="small" onClick={onDownloadResponses}>
|
||||
<GetAppIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Download screenshot of output"}>
|
||||
<IconButton size="small" onClick={onDownloadImageClickPng}>
|
||||
<IconButton className={responseActionClass("success")} size="small" onClick={onDownloadImageClickPng}>
|
||||
<InsertPhotoIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<Button size="small" variant="text" startIcon={<MoreHorizIcon fontSize="small" />} onClick={openActionsMenu}
|
||||
sx={{ml: 0.25, minWidth: "auto", textTransform: "none"}}>
|
||||
</Button>
|
||||
<MythicStyledTooltip title={"More response actions"}>
|
||||
<IconButton className={responseActionClass("info")} size="small" onClick={openActionsMenu}>
|
||||
<MoreHorizIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<Menu anchorEl={actionsMenuAnchor}
|
||||
open={Boolean(actionsMenuAnchor)}
|
||||
onClose={closeActionsMenu}
|
||||
anchorOrigin={{vertical: "bottom", horizontal: "right"}}
|
||||
transformOrigin={{vertical: "top", horizontal: "right"}}>
|
||||
<MenuItem onClick={()=>{setOpenTaskTagDialog(true);closeActionsMenu();}}>
|
||||
transformOrigin={{vertical: "top", horizontal: "right"}}
|
||||
MenuListProps={{className: "mythic-response-action-menu"}}>
|
||||
<MenuItem className={responseMenuItemClass("info")} onClick={()=>{setOpenTaskTagDialog(true);closeActionsMenu();}}>
|
||||
<ListItemIcon><LocalOfferOutlinedIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Edit Tags</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={()=> {window.open('/new/task/' + task.display_id, "_blank");closeActionsMenu();}}>
|
||||
<MenuItem className={responseMenuItemClass("info")} onClick={()=> {window.open('/new/task/' + task.display_id, "_blank");closeActionsMenu();}}>
|
||||
<ListItemIcon><FontAwesomeIcon icon={faExternalLinkAlt} size="sm" /></ListItemIcon>
|
||||
<ListItemText>Open Task in New Window</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={copyToClipboard}>
|
||||
<MenuItem className={responseMenuItemClass("info")} onClick={copyToClipboard}>
|
||||
<ListItemIcon><FileCopyOutlinedIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Copy original params</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={()=>{setOpenCommentDialog(true);closeActionsMenu();}}>
|
||||
<MenuItem className={responseMenuItemClass("info")} onClick={()=>{setOpenCommentDialog(true);closeActionsMenu();}}>
|
||||
<ListItemIcon><RateReviewOutlinedIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Edit Comment</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={()=>{setOpenParametersDialog(true);closeActionsMenu();}}>
|
||||
<MenuItem className={responseMenuItemClass("info")} onClick={()=>{setOpenParametersDialog(true);closeActionsMenu();}}>
|
||||
<ListItemIcon><KeyboardIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>View Parameters And Timestamps</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={()=>{setOpenStdoutStderrDialog(true);closeActionsMenu();}}>
|
||||
<ListItemIcon><FontAwesomeIcon style={{color: theme.palette.error.main}} icon={faExclamationTriangle} size="sm" /></ListItemIcon>
|
||||
<MenuItem className={responseMenuItemClass("danger")} onClick={()=>{setOpenStdoutStderrDialog(true);closeActionsMenu();}}>
|
||||
<ListItemIcon><FontAwesomeIcon icon={faExclamationTriangle} size="sm" /></ListItemIcon>
|
||||
<ListItemText>View Stdout/Stderr</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onTriggerEventing}>
|
||||
<MenuItem className={responseMenuItemClass("info")} onClick={onTriggerEventing}>
|
||||
<ListItemIcon><PlayCircleFilledTwoToneIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Trigger Eventing Based on Task</ListItemText>
|
||||
</MenuItem>
|
||||
@@ -696,42 +704,42 @@ const TaskActionsToolbarGeneric = ({toggleViewBrowserScript, toggleSelectAllOutp
|
||||
<Divider />
|
||||
}
|
||||
{task.opsec_pre_blocked !== null && (
|
||||
<MenuItem onClick={()=>{setOpenOpsecDialog({open: true, view: "pre"});closeActionsMenu();}}>
|
||||
<MenuItem className={responseMenuItemClass(task.opsec_pre_bypassed === false ? "danger" : "success")} onClick={()=>{setOpenOpsecDialog({open: true, view: "pre"});closeActionsMenu();}}>
|
||||
<ListItemIcon>
|
||||
{task.opsec_pre_bypassed === false ?
|
||||
<LockIcon fontSize="small" style={{color: theme.palette.error.main}}/> :
|
||||
<LockOpenIcon fontSize="small" style={{color: theme.palette.success.main}}/>
|
||||
<LockIcon fontSize="small" /> :
|
||||
<LockOpenIcon fontSize="small" />
|
||||
}
|
||||
</ListItemIcon>
|
||||
<ListItemText>{task.opsec_pre_bypassed === false ? "Submit OPSEC PreCheck Bypass Request" : "View OPSEC PreCheck Data"}</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
{task.opsec_post_blocked !== null && (
|
||||
<MenuItem onClick={()=>{setOpenOpsecDialog({open: true, view: "post"});closeActionsMenu();}}>
|
||||
<MenuItem className={responseMenuItemClass(task.opsec_post_bypassed === false ? "danger" : "success")} onClick={()=>{setOpenOpsecDialog({open: true, view: "post"});closeActionsMenu();}}>
|
||||
<ListItemIcon>
|
||||
{task.opsec_post_bypassed === false ?
|
||||
<LockIcon fontSize="small" style={{color: theme.palette.error.main}}/> :
|
||||
<LockOpenIcon fontSize="small" style={{color: theme.palette.success.main}}/>
|
||||
<LockIcon fontSize="small" /> :
|
||||
<LockOpenIcon fontSize="small" />
|
||||
}
|
||||
</ListItemIcon>
|
||||
<ListItemText>{task.opsec_post_bypassed === false ? "Submit OPSEC PostCheck Bypass Request" : "View OPSEC PostCheck Data"}</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
{task.token !== null && (
|
||||
<MenuItem onClick={()=>{setOpenTokenDialog(true);closeActionsMenu();}}>
|
||||
<MenuItem className={responseMenuItemClass("info")} onClick={()=>{setOpenTokenDialog(true);closeActionsMenu();}}>
|
||||
<ListItemIcon><ConfirmationNumberIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>View Token Information</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
{task.status.toLowerCase().includes("error: container") && (
|
||||
<MenuItem onClick={onReissueTask}>
|
||||
<ListItemIcon><ReplayIcon fontSize="small" style={{color: theme.palette.warning.main}} /></ListItemIcon>
|
||||
<MenuItem className={responseMenuItemClass("warning")} onClick={onReissueTask}>
|
||||
<ListItemIcon><ReplayIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Resubmit Tasking</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
{task.status.toLowerCase().includes("error: task") && (
|
||||
<MenuItem onClick={onReissueTaskHandler}>
|
||||
<ListItemIcon><ReplayIcon fontSize="small" style={{color: theme.palette.warning.main}} /></ListItemIcon>
|
||||
<MenuItem className={responseMenuItemClass("warning")} onClick={onReissueTaskHandler}>
|
||||
<ListItemIcon><ReplayIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Resubmit Task Handler</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {gql, useMutation} from '@apollo/client';
|
||||
import { snackActions } from '../../utilities/Snackbar';
|
||||
import MythicTextField from '../../MythicComponents/MythicTextField';
|
||||
import {Typography, Box, Chip} from '@mui/material';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import {
|
||||
MythicDialogBody,
|
||||
MythicDialogButton,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
MythicFormGrid
|
||||
} from "../../MythicComponents/MythicDialogLayout";
|
||||
import {isValidHexColor, MythicColorSwatchInput} from "../../MythicComponents/MythicColorInput";
|
||||
import {getTagReadableTextColor} from "../../MythicComponents/MythicTagChip";
|
||||
|
||||
const newTagtypeMutation = gql`
|
||||
mutation newTagType($name: String!, $description: String!, $color: String!) {
|
||||
@@ -33,9 +35,11 @@ mutation updateTagType($id: Int!, $name: String!, $description: String!, $color:
|
||||
`;
|
||||
|
||||
const TagColorPreview = ({mode, color, label}) => {
|
||||
const theme = useTheme();
|
||||
const darkMode = mode === "dark";
|
||||
const hasValidColor = isValidHexColor(color);
|
||||
const textColor = darkMode ? "#ffffff" : "#111827";
|
||||
const chipTextColor = hasValidColor ? getTagReadableTextColor(theme, color) : textColor;
|
||||
const surfaceColor = darkMode ? "#1f2937" : "#f8fafc";
|
||||
const surfaceBorderColor = darkMode ? "rgba(255,255,255,0.16)" : "rgba(17,24,39,0.12)";
|
||||
const chipBorderColor = hasValidColor ? (darkMode ? "rgba(255,255,255,0.2)" : "rgba(17,24,39,0.16)") : surfaceBorderColor;
|
||||
@@ -69,9 +73,10 @@ const TagColorPreview = ({mode, color, label}) => {
|
||||
border: "1px solid",
|
||||
borderColor: chipBorderColor,
|
||||
borderStyle: hasValidColor ? "solid" : "dashed",
|
||||
color: textColor,
|
||||
color: chipTextColor,
|
||||
fontWeight: 800,
|
||||
"& .MuiChip-label": {
|
||||
color: "inherit",
|
||||
px: 1,
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
|
||||
@@ -7,8 +7,8 @@ import {MythicConfirmDialog} from '../../MythicComponents/MythicConfirmDialog';
|
||||
import { MythicStyledTooltip } from '../../MythicComponents/MythicStyledTooltip';
|
||||
import MythicStyledTableCell from '../../MythicComponents/MythicTableCell';
|
||||
import {NewTagtypesDialog} from './NewTagtypesDialog';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import {TagTypeChip} from "../../MythicComponents/MythicTagChip";
|
||||
|
||||
|
||||
export function TagtypesTableRow(props){
|
||||
@@ -49,7 +49,7 @@ export function TagtypesTableRow(props){
|
||||
{props.tags_aggregate.aggregate.count}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Chip label={props.name} size="small" style={{backgroundColor:props.color}} />
|
||||
<TagTypeChip tagtype={props} />
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{props.description}
|
||||
|
||||
@@ -2410,6 +2410,103 @@ tspan {
|
||||
line-height: 1.4;
|
||||
padding: 0.65rem 0.75rem;
|
||||
}
|
||||
.mythic-tag-editor-frame {
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-tag-editor-frame .ace_editor {
|
||||
background-color: ${(props) => props.theme.palette.background.paper} !important;
|
||||
}
|
||||
.mythic-tag-data-split {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-tag-data-preview-frame {
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
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};
|
||||
max-height: 30rem;
|
||||
min-height: 10rem;
|
||||
min-width: 0;
|
||||
overflow: auto;
|
||||
padding: 0.55rem;
|
||||
}
|
||||
.mythic-tag-data-preview-frame-full {
|
||||
max-height: min(48vh, 34rem);
|
||||
}
|
||||
.mythic-tag-readonly-value {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
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: flex;
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.35;
|
||||
min-height: 34px;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
padding: 0.45rem 0.6rem;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-tag-data-preview-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-tag-data-preview-row {
|
||||
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: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: minmax(7rem, 0.35fr) minmax(0, 1fr);
|
||||
min-width: 0;
|
||||
padding: 0.45rem 0.55rem;
|
||||
}
|
||||
.mythic-tag-data-preview-key {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.76rem;
|
||||
font-weight: 800;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-tag-data-preview-value {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.35;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-tag-data-preview-value pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.mythic-tag-data-preview-empty {
|
||||
align-items: center;
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
display: flex;
|
||||
font-size: 0.82rem;
|
||||
justify-content: center;
|
||||
min-height: 9rem;
|
||||
text-align: center;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.mythic-tag-data-split {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.mythic-form-switch-row {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
@@ -6894,6 +6991,70 @@ tspan {
|
||||
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
color: ${(props) => props.theme.palette.text.disabled} !important;
|
||||
}
|
||||
.MuiIconButton-root.mythic-response-action-button {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.045) : alpha(props.theme.palette.common.black, 0.028)} !important;
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px !important;
|
||||
box-shadow: none !important;
|
||||
color: ${(props) => props.theme.palette.text.secondary} !important;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease, box-shadow 140ms ease;
|
||||
width: 30px;
|
||||
}
|
||||
.MuiIconButton-root.mythic-response-action-button .MuiSvgIcon-root,
|
||||
.MuiIconButton-root.mythic-response-action-button .svg-inline--fa {
|
||||
color: inherit !important;
|
||||
}
|
||||
.MuiIconButton-root.mythic-response-action-button:hover {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.085) : alpha(props.theme.palette.common.black, 0.052)} !important;
|
||||
border-color: ${(props) => props.theme.table?.border || props.theme.borderColor} !important;
|
||||
color: ${(props) => props.theme.palette.text.primary} !important;
|
||||
}
|
||||
.mythic-response-action-menu {
|
||||
padding: 0.25rem !important;
|
||||
}
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-menu-item {
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
min-height: 34px;
|
||||
}
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-menu-item .MuiListItemIcon-root {
|
||||
color: inherit;
|
||||
min-width: 2rem;
|
||||
}
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-menu-item .MuiSvgIcon-root,
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-menu-item .svg-inline--fa {
|
||||
color: inherit !important;
|
||||
}
|
||||
.MuiIconButton-root.mythic-response-action-button.mythic-response-action-hover-info:hover,
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-hover-info:hover,
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-hover-info.Mui-focusVisible {
|
||||
background-color: ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.54 : 0.36)} !important;
|
||||
color: ${(props) => props.theme.palette.info.main} !important;
|
||||
}
|
||||
.MuiIconButton-root.mythic-response-action-button.mythic-response-action-hover-success:hover,
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-hover-success:hover,
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-hover-success.Mui-focusVisible {
|
||||
background-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.54 : 0.36)} !important;
|
||||
color: ${(props) => props.theme.palette.success.main} !important;
|
||||
}
|
||||
.MuiIconButton-root.mythic-response-action-button.mythic-response-action-hover-warning:hover,
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-hover-warning:hover,
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-hover-warning.Mui-focusVisible {
|
||||
background-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.2 : 0.12)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)} !important;
|
||||
color: ${(props) => props.theme.palette.warning.main} !important;
|
||||
}
|
||||
.MuiIconButton-root.mythic-response-action-button.mythic-response-action-hover-danger:hover,
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-hover-danger:hover,
|
||||
.mythic-response-action-menu .MuiMenuItem-root.mythic-response-action-hover-danger.Mui-focusVisible {
|
||||
background-color: ${(props) => alpha(props.theme.palette.error.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.error.main, props.theme.palette.mode === "dark" ? 0.56 : 0.38)} !important;
|
||||
color: ${(props) => props.theme.palette.error.main} !important;
|
||||
}
|
||||
.mythic-menu-item-hover-danger .MuiSvgIcon-root {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package webcontroller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/its-a-feature/Mythic/database"
|
||||
"github.com/its-a-feature/Mythic/eventing"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
|
||||
@@ -72,14 +74,16 @@ func TagCreateWebhook(c *gin.Context) {
|
||||
Source: input.Input.Source,
|
||||
TagTypeID: input.Input.TagTypeID,
|
||||
}
|
||||
switch input.Input.Data.(type) {
|
||||
case string:
|
||||
var jsonData json.RawMessage
|
||||
err = json.Unmarshal([]byte(input.Input.Data.(string)), &jsonData)
|
||||
if err != nil {
|
||||
databaseObj.Data = rabbitmq.GetMythicJSONTextFromStruct(map[string]interface{}{
|
||||
"input": input.Input.Data,
|
||||
})
|
||||
default:
|
||||
databaseObj.Data = rabbitmq.GetMythicJSONTextFromStruct(input.Input.Data)
|
||||
} else {
|
||||
databaseObj.Data = rabbitmq.GetMythicJSONTextFromStruct(jsonData)
|
||||
}
|
||||
|
||||
APITokenID, ok := c.Get("apitokens-id")
|
||||
associatedWithValidObject := false
|
||||
if ok {
|
||||
|
||||
Reference in New Issue
Block a user