mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
updated eventing and installed services tables
This commit is contained in:
@@ -150,7 +150,7 @@ export const ContextMenu = ({openContextMenu, dropdownAnchorRef, contextMenuOpti
|
||||
<DropdownMenuItem
|
||||
key={option.name}
|
||||
disabled={option.disabled}
|
||||
className={option.danger ? "mythic-menu-item-hover-danger" : undefined}
|
||||
className={option.className || (option.danger ? "mythic-menu-item-hover-danger" : undefined)}
|
||||
onClick={(event) => handleMenuItemClick(event, option.click)}
|
||||
>
|
||||
{option.icon}{option.name}
|
||||
@@ -164,7 +164,7 @@ export const ContextMenu = ({openContextMenu, dropdownAnchorRef, contextMenuOpti
|
||||
<DropdownMenuItem
|
||||
key={menuOption.name}
|
||||
disabled={menuOption.disabled}
|
||||
className={menuOption.danger ? "mythic-menu-item-hover-danger" : undefined}
|
||||
className={menuOption.className || (menuOption.danger ? "mythic-menu-item-hover-danger" : undefined)}
|
||||
onClick={(event) => handleMenuItemClick(event, menuOption.click)}
|
||||
>
|
||||
{menuOption.icon}{menuOption.name}
|
||||
|
||||
@@ -296,7 +296,7 @@ export function EventFeed({}){
|
||||
const resolveViewableErrors = useCallback( () => {
|
||||
snackActions.info("Resolving Errors...");
|
||||
const resolveIds = operationeventlog.reduce( (prev, cur) => {
|
||||
if(cur.level === "warning" && !cur.resolved){
|
||||
if(cur.warning && !cur.resolved){
|
||||
return [...prev, cur.id];
|
||||
}else{
|
||||
return [...prev];
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, {} from 'react';
|
||||
import {EventStepInstanceRenderDialog, GetStatusSymbol} from "./EventStepRender";
|
||||
import {EventingStatusChip, EventStepInstanceRenderDialog} from "./EventStepRender";
|
||||
import {toLocalTime} from "../../utilities/Time";
|
||||
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
|
||||
import {MythicConfirmDialog} from '../../MythicComponents/MythicConfirmDialog';
|
||||
import { gql, useMutation } from '@apollo/client';
|
||||
import CalendarMonthTwoToneIcon from '@mui/icons-material/CalendarMonthTwoTone';
|
||||
import AccessAlarmTwoToneIcon from '@mui/icons-material/AccessAlarmTwoTone';
|
||||
import CancelTwoToneIcon from '@mui/icons-material/CancelTwoTone';
|
||||
import ReplayIcon from '@mui/icons-material/Replay';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||
import Moment from 'react-moment';
|
||||
import moment from 'moment';
|
||||
import CastConnectedTwoToneIcon from '@mui/icons-material/CastConnectedTwoTone';
|
||||
import {snackActions} from "../../utilities/Snackbar";
|
||||
import {MythicDialog} from "../../MythicComponents/MythicDialog";
|
||||
import OpenInNewTwoToneIcon from '@mui/icons-material/OpenInNewTwoTone';
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
gridValuePassesFilter,
|
||||
isGridColumnFilterActive
|
||||
} from "../../MythicComponents/MythicResizableGrid/GridColumnFilterDialog";
|
||||
import {Dropdown, DropdownMenuItem} from "../../MythicComponents/MythicNestedMenus";
|
||||
|
||||
const cancelEventGroupInstanceMutation = gql(`
|
||||
mutation cancelEventGroupInstanceMutation($eventgroupinstance_id: Int!){
|
||||
@@ -83,6 +84,29 @@ export const adjustOutput = (e, newTime) => {
|
||||
return newTime;
|
||||
}
|
||||
|
||||
const EventingGridCell = ({children, className = "", rowData}) => (
|
||||
<div className={`mythic-eventing-instance-cell ${className}`.trim()} data-selected={rowData?.selected ? "true" : undefined}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
const eventingInstanceMenuIconStyle = {fontSize: "1rem", marginRight: "8px"};
|
||||
const EventingInstanceIdCell = ({onOpenMenu, rowData}) => (
|
||||
<EventingGridCell className="mythic-eventing-instance-id-cell" rowData={rowData}>
|
||||
<span className="mythic-eventing-instance-id">{rowData.id}</span>
|
||||
<IconButton
|
||||
aria-haspopup="menu"
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info mythic-eventing-instance-id-menu-button"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onOpenMenu({event, rowData});
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<ArrowDropDownIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</EventingGridCell>
|
||||
);
|
||||
|
||||
function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, setSelectedInstance, selectedInstanceID}){
|
||||
const callbackTableGridRef = React.useRef();
|
||||
@@ -91,6 +115,8 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
const [openRetryDialog, setOpenRetryDialog] = React.useState(false);
|
||||
const [openRunAgainDialog, setOpenRunAgainDialog] = React.useState(false);
|
||||
const [openEventStepRender, setOpenEventStepRender] = React.useState(false);
|
||||
const [openInstanceDropdown, setOpenInstanceDropdown] = React.useState(false);
|
||||
const instanceDropdownRef = React.useRef({options: [], anchor: null});
|
||||
const selectedLocalInstanceID = React.useRef(0);
|
||||
const selectedLocalEventGroup = React.useRef({});
|
||||
const foundQueryEvent = React.useRef(false);
|
||||
@@ -162,6 +188,90 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
copyStringToClipboard(path);
|
||||
snackActions.success("copied shareable link to clipboard");
|
||||
}
|
||||
const getInstanceMenuOptions = (row) => {
|
||||
if(!row?.id){
|
||||
return [];
|
||||
}
|
||||
const controlOptions = [];
|
||||
if(row.end_timestamp === null){
|
||||
controlOptions.push({
|
||||
name: "Cancel workflow",
|
||||
type: "item",
|
||||
icon: <CancelTwoToneIcon style={eventingInstanceMenuIconStyle} />,
|
||||
className: "mythic-menu-item-hover-warning",
|
||||
click: ({event}) => {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
onOpenCancelDialog({id: row.id});
|
||||
},
|
||||
});
|
||||
}else if(row.status === "error" || row.status === "cancelled"){
|
||||
controlOptions.push({
|
||||
name: "Retry failed / cancelled steps",
|
||||
type: "item",
|
||||
icon: <ReplayIcon style={eventingInstanceMenuIconStyle} />,
|
||||
className: "mythic-menu-item-hover-warning",
|
||||
click: ({event}) => {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
onOpenRetryDialog({id: row.id});
|
||||
},
|
||||
});
|
||||
}else if(row.status === "success"){
|
||||
controlOptions.push({
|
||||
name: "Run again",
|
||||
type: "item",
|
||||
icon: <ReplayIcon style={eventingInstanceMenuIconStyle} />,
|
||||
className: "mythic-menu-item-hover-success",
|
||||
click: ({event}) => {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
onOpenRunAgainDialog({id: row.id});
|
||||
},
|
||||
});
|
||||
}
|
||||
return [
|
||||
{
|
||||
name: "Open graph in modal",
|
||||
type: "item",
|
||||
icon: <OpenInNewTwoToneIcon style={eventingInstanceMenuIconStyle} />,
|
||||
className: "mythic-menu-item-hover-info",
|
||||
click: ({event}) => {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
openViewInstanceLargeDialog(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Copy shareable link",
|
||||
type: "item",
|
||||
icon: <IosShareIcon style={eventingInstanceMenuIconStyle} />,
|
||||
className: "mythic-menu-item-hover-info",
|
||||
click: ({event}) => {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
onSaveToClipboard(row);
|
||||
},
|
||||
},
|
||||
...controlOptions,
|
||||
];
|
||||
}
|
||||
const openInstanceMenu = ({event, rowData}) => {
|
||||
instanceDropdownRef.current = {
|
||||
anchor: event?.currentTarget || event?.target,
|
||||
options: getInstanceMenuOptions(rowData),
|
||||
};
|
||||
setOpenInstanceDropdown(true);
|
||||
}
|
||||
const closeInstanceMenu = () => {
|
||||
setOpenInstanceDropdown(false);
|
||||
}
|
||||
const handleInstanceMenuItemClick = (event, clickOption) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
clickOption({event});
|
||||
setOpenInstanceDropdown(false);
|
||||
}
|
||||
const [sortData, setSortData] = React.useState({"sortKey": null, "sortDirection": null, "sortType": null});
|
||||
const [filterOptions, setFilterOptions] = React.useState({});
|
||||
React.useEffect( () => {
|
||||
@@ -181,13 +291,12 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
const columns = React.useMemo(
|
||||
() =>
|
||||
[
|
||||
{key: "id", type: "number", name: "ID", width: 60, enableHiding: false, disableSort: false, disableFilter: true},
|
||||
{key: "status", type: 'string', name: "Status", width: 150, disableSort: true, enableHiding: false},
|
||||
{key: "id", type: "number", name: "ID", width: 92, enableHiding: false, disableSort: false, disableFilter: true},
|
||||
{key: "status", type: 'string', name: "Status", width: 140, disableSort: false, enableHiding: false},
|
||||
{key: "eventgroup", type: 'string', name: "Event Group", inMetadata: true, fillWidth: true, disableSort: false, enableHiding: false},
|
||||
{key: "trigger", type: 'string', name: "Trigger", fillWidth: true, disableSort: false, enableHiding: false},
|
||||
{key: "time", type: 'date', name: "Time", width: 300, disableSort: true,},
|
||||
{key: "operator", type: 'string', name: "Operator", inMetadata: true, fillWidth: true, disableSort: true,},
|
||||
{key: "cancel", type: 'string', name: "Action", width: 70, disableSort: true, disableFilter: true},
|
||||
]?.reduce( (prev, cur) => {
|
||||
if(isGridColumnFilterActive(filterOptions[cur.key])){
|
||||
return [...prev, {...cur, filtered: true}];
|
||||
@@ -285,50 +394,24 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
if(filterRow(row)){
|
||||
return [...prev];
|
||||
}
|
||||
const selectedRow = {...row, selected: row.id === selectedInstanceID};
|
||||
return [...prev, columns.map( c => {
|
||||
switch(c.name){
|
||||
case "ID":
|
||||
return <CallbacksTableStringCell rowData={row} cellData={row.id} />
|
||||
return <EventingInstanceIdCell rowData={selectedRow} onOpenMenu={openInstanceMenu} />
|
||||
case "Status":
|
||||
return (
|
||||
<div className="mythic-table-row-actions mythic-table-row-actions-nowrap">
|
||||
<GetStatusSymbol data={row} />
|
||||
{
|
||||
selectedInstanceID === 0 ?
|
||||
(
|
||||
<MythicStyledTooltip title={"View Graph Above"} >
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => {setSelectedInstance(row.id);}} >
|
||||
<CastConnectedTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) :
|
||||
(
|
||||
<MythicStyledTooltip title={"Stop viewing graph"} >
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-danger" size="small" onClick={() => {setSelectedInstance(0);}} >
|
||||
<CancelTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)
|
||||
}
|
||||
<MythicStyledTooltip title={"Open Graph in Modal"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => {openViewInstanceLargeDialog(row)}}>
|
||||
<OpenInNewTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Copy shareable link to workflow"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => onSaveToClipboard(row)}>
|
||||
<IosShareIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</div>
|
||||
<EventingGridCell className="mythic-eventing-instance-status-cell" rowData={selectedRow}>
|
||||
<EventingStatusChip data={row} />
|
||||
</EventingGridCell>
|
||||
)
|
||||
case "Event Group":
|
||||
return <CallbacksTableStringCell rowData={row} cellData={row.eventgroup.name} />
|
||||
return <CallbacksTableStringCell rowData={selectedRow} cellData={row.eventgroup.name} />
|
||||
case "Trigger":
|
||||
return <CallbacksTableStringCell rowData={row} cellData={row.trigger} />
|
||||
return <CallbacksTableStringCell rowData={selectedRow} cellData={row.trigger} />
|
||||
case "Time":
|
||||
return (
|
||||
<div className="mythic-eventing-instances-time-cell">
|
||||
<EventingGridCell className="mythic-eventing-instances-time-cell" rowData={selectedRow}>
|
||||
<div className="mythic-eventing-instances-time-line">
|
||||
<CalendarMonthTwoToneIcon fontSize="small" />
|
||||
{toLocalTime(row?.created_at, me?.user?.view_utc_time)}
|
||||
@@ -355,42 +438,14 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
</Moment>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</EventingGridCell>
|
||||
)
|
||||
case "Operator":
|
||||
return <CallbacksTableStringCell rowData={row} cellData={row?.operator?.username} />
|
||||
case "Action":
|
||||
return (
|
||||
<div className="mythic-table-row-actions mythic-table-row-actions-nowrap">
|
||||
{row.end_timestamp === null ? (
|
||||
<MythicStyledTooltip title={"Cancel Eventing"} >
|
||||
<IconButton onClick={() => {onOpenCancelDialog({id: row.id});}}
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-warning" size="small">
|
||||
<CancelTwoToneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) : row.status === "error" || row.status === "cancelled" ? (
|
||||
<MythicStyledTooltip title={"Retry Failed / Canceled Steps"} >
|
||||
<IconButton onClick={() => {onOpenRetryDialog({id: row.id});}}
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-warning" size="small">
|
||||
<ReplayIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) : row.status === "success" ? (
|
||||
<MythicStyledTooltip title={"Run Again"} >
|
||||
<IconButton onClick={() => {onOpenRunAgainDialog({id: row.id});}}
|
||||
className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-success" size="small">
|
||||
<ReplayIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
)
|
||||
return <CallbacksTableStringCell rowData={selectedRow} cellData={row?.operator?.username} />
|
||||
}
|
||||
})]
|
||||
}, []);
|
||||
}, [sortData, filterOptions, selectedInstanceID, eventgroups]);
|
||||
}, [sortData, filterOptions, selectedInstanceID, eventgroups, columns]);
|
||||
const sortColumn = columns.findIndex((column) => column.key === sortData.sortKey);
|
||||
const [openContextMenu, setOpenContextMenu] = React.useState(false);
|
||||
const [selectedColumn, setSelectedColumn] = React.useState({});
|
||||
@@ -412,9 +467,21 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
},];
|
||||
const onRowDoubleClick = () => {
|
||||
|
||||
}
|
||||
const onRowClick = ({event, rowDataStatic}) => {
|
||||
if(event.target?.closest?.("button") || event.target?.closest?.("a")){
|
||||
return;
|
||||
}
|
||||
if(rowDataStatic?.id === selectedInstanceID){
|
||||
setSelectedInstance(0);
|
||||
return;
|
||||
}
|
||||
if(rowDataStatic?.id){
|
||||
setSelectedInstance(rowDataStatic.id);
|
||||
}
|
||||
}
|
||||
const onRowContextClick = ({rowDataStatic}) => {
|
||||
return [];
|
||||
return getInstanceMenuOptions(rowDataStatic);
|
||||
}
|
||||
return (
|
||||
<div className="mythic-eventing-instances-grid">
|
||||
@@ -428,6 +495,7 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
rowHeight={eventingRowHeight}
|
||||
onClickHeader={onClickHeader}
|
||||
onDoubleClickRow={onRowDoubleClick}
|
||||
onRowClick={onRowClick}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
onRowContextMenuClick={onRowContextClick}
|
||||
/>
|
||||
@@ -446,6 +514,28 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
|
||||
}
|
||||
/>
|
||||
}
|
||||
{openInstanceDropdown &&
|
||||
<ClickAwayListener onClickAway={closeInstanceMenu} mouseEvent={"onMouseDown"}>
|
||||
<Dropdown
|
||||
isOpen={instanceDropdownRef.current.anchor}
|
||||
onOpen={setOpenInstanceDropdown}
|
||||
externallyOpen={openInstanceDropdown}
|
||||
minWidth={250}
|
||||
menu={
|
||||
instanceDropdownRef.current.options.map((option, index) => (
|
||||
<DropdownMenuItem
|
||||
key={"eventing-instance-action-" + index}
|
||||
className={option.className}
|
||||
disabled={option.disabled}
|
||||
onClick={(event) => handleInstanceMenuItemClick(event, option.click)}
|
||||
>
|
||||
{option.icon}{option.name}
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
/>
|
||||
</ClickAwayListener>
|
||||
}
|
||||
{openDeleteDialog &&
|
||||
<MythicConfirmDialog onClose={() => {
|
||||
setOpenDeleteDialog(false);
|
||||
|
||||
@@ -89,6 +89,7 @@ subscription GetEventStepInstances($eventgroupinstance_id: Int!) {
|
||||
order
|
||||
action
|
||||
}
|
||||
eventgroupinstance_id
|
||||
created_at
|
||||
updated_at
|
||||
end_timestamp
|
||||
@@ -109,7 +110,6 @@ query getEventStepInstanceDetails($eventstepinstance_id: Int!){
|
||||
created_at
|
||||
order
|
||||
end_timestamp
|
||||
action
|
||||
action_data
|
||||
stdout
|
||||
stderr
|
||||
@@ -399,7 +399,7 @@ const getStatusClass = (status) => {
|
||||
}
|
||||
const hasEventingStatus = (status) => status !== undefined && status !== null && status !== "";
|
||||
const getEventingStatusClass = (status) => hasEventingStatus(status) ? getStatusClass(status) : "configured";
|
||||
const EventingStatusChip = ({data}) => {
|
||||
export const EventingStatusChip = ({data}) => {
|
||||
const hasStatus = hasEventingStatus(data?.status);
|
||||
return (
|
||||
<span className={`mythic-eventing-status-chip mythic-eventing-status-chip-${getEventingStatusClass(data?.status)}`.trim()}>
|
||||
@@ -429,11 +429,46 @@ const stringifyEventingValue = (value) => {
|
||||
}
|
||||
}
|
||||
const EventingCodeBlock = ({value, emptyText="No data"}) => {
|
||||
const theme = useTheme();
|
||||
const displayValue = stringifyEventingValue(value);
|
||||
const formattedValue = React.useMemo(() => {
|
||||
if(displayValue === ""){
|
||||
return "";
|
||||
}
|
||||
try{
|
||||
return JSON.stringify(JSON.parse(displayValue), null, 2);
|
||||
}catch(error){
|
||||
return JSON.stringify(displayValue, null, 2);
|
||||
}
|
||||
}, [displayValue]);
|
||||
const lineCount = Math.max(3, Math.min(18, formattedValue.split("\n").length));
|
||||
return (
|
||||
<pre className={`mythic-eventing-code-block ${displayValue === "" ? "mythic-eventing-code-block-empty" : ""}`.trim()}>
|
||||
{displayValue === "" ? emptyText : displayValue}
|
||||
</pre>
|
||||
<div className={`mythic-eventing-code-block ${displayValue === "" ? "mythic-eventing-code-block-empty" : ""}`.trim()}>
|
||||
{displayValue === "" ? (
|
||||
emptyText
|
||||
) : (
|
||||
<AceEditor
|
||||
mode="json"
|
||||
theme={theme.palette.mode === "dark" ? "monokai" : "xcode"}
|
||||
fontSize={13}
|
||||
showGutter={true}
|
||||
highlightActiveLine={false}
|
||||
readOnly={true}
|
||||
showPrintMargin={false}
|
||||
value={formattedValue}
|
||||
width="100%"
|
||||
minLines={lineCount}
|
||||
maxLines={18}
|
||||
wrapEnabled={true}
|
||||
setOptions={{
|
||||
showLineNumbers: true,
|
||||
tabSize: 2,
|
||||
useWorker: false,
|
||||
}}
|
||||
editorProps={{$blockScrolling: true}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const EventingDetailSection = ({title, subtitle, count, actions, children, className = "", collapsible = false, defaultExpanded = false}) => {
|
||||
@@ -862,6 +897,7 @@ function EventStepInstanceRender({selectedEventGroupInstance}) {
|
||||
const [graphData, setGraphData] = React.useState({nodes: [], edges: [], groups: []});
|
||||
const {fitView} = useReactFlow()
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const selectedEventGroupInstanceRef = React.useRef(selectedEventGroupInstance);
|
||||
const [retryFromEventStep] = useMutation(retryFromEventStepMutation, {
|
||||
onCompleted: (data) => {
|
||||
if(data.eventingTriggerRetryFromStep.status === "success"){
|
||||
@@ -883,21 +919,33 @@ function EventStepInstanceRender({selectedEventGroupInstance}) {
|
||||
[]
|
||||
);
|
||||
const viewportRef = React.useRef(null);
|
||||
React.useEffect(() => {
|
||||
selectedEventGroupInstanceRef.current = selectedEventGroupInstance;
|
||||
setSteps([]);
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
setGraphData({nodes: [], edges: [], groups: []});
|
||||
setOpenContextMenu(false);
|
||||
}, [selectedEventGroupInstance]);
|
||||
useSubscription(sub_eventstepinstance, {
|
||||
variables: {eventgroupinstance_id: selectedEventGroupInstance},
|
||||
fetchPolicy: "no-cache",
|
||||
onData: ({data}) => {
|
||||
const newSteps = data.data.eventstepinstance_stream.reduce( (prev, cur) => {
|
||||
let indx = prev.findIndex( ({id}) => id === cur.id);
|
||||
if(indx > -1){
|
||||
let updatingPrev = [...prev];
|
||||
updatingPrev[indx] = cur;
|
||||
return [...updatingPrev];
|
||||
}
|
||||
return [...prev, cur];
|
||||
}, [...steps]);
|
||||
newSteps.sort((a,b) => (a.id > b.id) ? -1 : ((b.id > a.id) ? 1 : 0));
|
||||
setSteps(newSteps);
|
||||
const activeEventGroupInstance = selectedEventGroupInstanceRef.current;
|
||||
setSteps((previousSteps) => {
|
||||
const incomingSteps = data.data.eventstepinstance_stream.filter((cur) => `${cur.eventgroupinstance_id}` === `${activeEventGroupInstance}`);
|
||||
const newSteps = incomingSteps.reduce( (prev, cur) => {
|
||||
let indx = prev.findIndex( ({id}) => id === cur.id);
|
||||
if(indx > -1){
|
||||
let updatingPrev = [...prev];
|
||||
updatingPrev[indx] = cur;
|
||||
return [...updatingPrev];
|
||||
}
|
||||
return [...prev, cur];
|
||||
}, [...previousSteps]);
|
||||
newSteps.sort((a,b) => (a.id > b.id) ? -1 : ((b.id > a.id) ? 1 : 0));
|
||||
return newSteps;
|
||||
});
|
||||
}
|
||||
});
|
||||
const contextMenu = React.useMemo(() => {return [
|
||||
@@ -1079,6 +1127,9 @@ function EventStepInstanceRender({selectedEventGroupInstance}) {
|
||||
}
|
||||
fitView();
|
||||
});
|
||||
} else {
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
}
|
||||
})();
|
||||
}, [graphData]);
|
||||
|
||||
@@ -33,8 +33,7 @@ import TableRow from '@mui/material/TableRow';
|
||||
import MythicTableCell from "../../MythicComponents/MythicTableCell";
|
||||
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
|
||||
import {MythicAgentSVGIcon} from "../../MythicComponents/MythicAgentSVGIcon";
|
||||
import {MythicStatusChip} from "../../MythicComponents/MythicStatusChip";
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||
import {C2ProfileStatusSummary} from "./InstalledServiceStatus";
|
||||
|
||||
|
||||
const toggleDeleteStatus = gql`
|
||||
@@ -148,8 +147,6 @@ export function C2ProfilesRow({service, showDeleted}) {
|
||||
if(service.deleted && !showDeleted){
|
||||
return null;
|
||||
}
|
||||
const containerStatus = !service.container_running ? "error" : service.is_p2p ? "success" : "neutral";
|
||||
const containerIcon = service.container_running && !service.is_p2p ? <CheckCircleOutlineIcon /> : undefined;
|
||||
return (
|
||||
<>
|
||||
<TableRow hover>
|
||||
@@ -178,7 +175,10 @@ export function C2ProfilesRow({service, showDeleted}) {
|
||||
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{service.name}
|
||||
<div className="mythic-installed-service-identity">
|
||||
<span className="mythic-installed-service-name">{service.name}</span>
|
||||
<C2ProfileStatusSummary service={service} />
|
||||
</div>
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>{service.is_p2p ? "P2P" : "Egress"}</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
@@ -197,29 +197,6 @@ export function C2ProfilesRow({service, showDeleted}) {
|
||||
<b>Description: </b>{service.description}
|
||||
</Typography>
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
<Typography variant="body2" component="p" >
|
||||
<b>Container Status: </b>
|
||||
</Typography>
|
||||
<MythicStatusChip
|
||||
label={service.container_running ? "Online" : "Offline"}
|
||||
icon={containerIcon}
|
||||
status={containerStatus}
|
||||
showIcon
|
||||
/>
|
||||
{!service.is_p2p &&
|
||||
<React.Fragment>
|
||||
<Typography variant="body2" component="p" >
|
||||
<b>C2 Server Status: </b>
|
||||
</Typography>
|
||||
<MythicStatusChip
|
||||
label={!service.container_running ? "Unavailable" : service.running ? "Accepting Connections" : "Not Accepting Connections"}
|
||||
status={!service.container_running ? "neutral" : service.running ? "success" : "error"}
|
||||
showIcon={service.container_running}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{service.container_running ? (
|
||||
service.running ?
|
||||
|
||||
@@ -20,7 +20,7 @@ import {MythicDialog} from "../../MythicComponents/MythicDialog";
|
||||
import AttachFileIcon from '@mui/icons-material/AttachFile';
|
||||
import {ConsumingServicesGetIDPMetadataDialog} from "./ConsumingServicesGetIDPMetadataDialog";
|
||||
import {C2ProfileListFilesDialog} from "./C2ProfileListFilesDialog";
|
||||
import {MythicStatusChip} from "../../MythicComponents/MythicStatusChip";
|
||||
import {InstalledServiceContainerStatus} from "./InstalledServiceStatus";
|
||||
|
||||
const testWebhookMutation = gql`
|
||||
mutation testWebhookWorks($service_type: String!){
|
||||
@@ -166,10 +166,7 @@ export const ConsumingServicesTableRow = ({service, showDeleted}) => {
|
||||
</MythicStyledTooltip>
|
||||
);
|
||||
const renderContainerStatus = (w) => (
|
||||
<MythicStatusChip
|
||||
label={w.container_running ? "Online" : "Offline"}
|
||||
status={w.container_running ? "success" : "error"}
|
||||
/>
|
||||
<InstalledServiceContainerStatus isOnline={w.container_running} />
|
||||
);
|
||||
const renderFileButton = (w) => (
|
||||
<MythicStyledTooltip title={w.container_running ? "View Files" : "Unable to view files since container is offline"}>
|
||||
@@ -196,13 +193,13 @@ export const ConsumingServicesTableRow = ({service, showDeleted}) => {
|
||||
<Typography variant={"h5"}>
|
||||
{w.name}
|
||||
</Typography>
|
||||
{renderContainerStatus(w)}
|
||||
<Typography variant={"body"}>
|
||||
<b>Type: </b>{w.type}
|
||||
</Typography>
|
||||
<Typography variant={"body2"}>
|
||||
<b>Description: </b>{w.description}
|
||||
</Typography>
|
||||
{renderContainerStatus(w)}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{renderFileButton(w)}
|
||||
@@ -237,13 +234,13 @@ export const ConsumingServicesTableRow = ({service, showDeleted}) => {
|
||||
<Typography variant={"h5"}>
|
||||
{w.name}
|
||||
</Typography>
|
||||
{renderContainerStatus(w)}
|
||||
<Typography variant={"body"}>
|
||||
<b>Type: </b>{w.type}
|
||||
</Typography>
|
||||
<Typography variant={"body2"}>
|
||||
<b>Description: </b>{w.description}
|
||||
</Typography>
|
||||
{renderContainerStatus(w)}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{renderFileButton(w)}
|
||||
@@ -278,13 +275,13 @@ export const ConsumingServicesTableRow = ({service, showDeleted}) => {
|
||||
<Typography variant={"h5"}>
|
||||
{w.name}
|
||||
</Typography>
|
||||
{renderContainerStatus(w)}
|
||||
<Typography variant={"body"}>
|
||||
<b>Type: </b>{w.type}
|
||||
</Typography>
|
||||
<Typography variant={"body2"}>
|
||||
<b>Description: </b>{w.description}
|
||||
</Typography>
|
||||
{renderContainerStatus(w)}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{renderFileButton(w)}
|
||||
@@ -321,13 +318,13 @@ export const ConsumingServicesTableRow = ({service, showDeleted}) => {
|
||||
<Typography variant={"h5"}>
|
||||
{w.name}
|
||||
</Typography>
|
||||
{renderContainerStatus(w)}
|
||||
<Typography variant={"body"}>
|
||||
<b>Type: </b>{w.type}
|
||||
</Typography>
|
||||
<Typography variant={"body2"}>
|
||||
<b>Description: </b>{w.description}
|
||||
</Typography>
|
||||
{renderContainerStatus(w)}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{renderFileButton(w)}
|
||||
|
||||
@@ -18,7 +18,7 @@ import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import {MythicStatusChip} from "../../MythicComponents/MythicStatusChip";
|
||||
import {InstalledServiceContainerStatus} from "./InstalledServiceStatus";
|
||||
|
||||
const toggleDeleteStatus = gql`
|
||||
mutation toggleCustomBrowserDeleteStatus($custombrowser_id: Int!, $deleted: Boolean!){
|
||||
@@ -69,9 +69,9 @@ export function CustomBrowserRow({service, showDeleted}) {
|
||||
<FontAwesomeIcon icon={faFolderOpen} style={{width: "60px", height: "60px"}} />
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{service.name}
|
||||
<div style={{marginTop: "0.35rem"}}>
|
||||
<MythicStatusChip label={service.container_running ? "Online" : "Offline"} status={service.container_running ? "success" : "error"} />
|
||||
<div className="mythic-installed-service-identity">
|
||||
<span className="mythic-installed-service-name">{service.name}</span>
|
||||
<InstalledServiceContainerStatus isOnline={service.container_running} />
|
||||
</div>
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
|
||||
const getServiceStatusTone = (isOnline, fallbackTone = "success") => isOnline ? fallbackTone : "error";
|
||||
|
||||
const InstalledServiceStatusSummary = ({label, tone, details = []}) => (
|
||||
<div className={`mythic-service-status-summary mythic-service-status-summary-${tone}`.trim()}>
|
||||
<div className="mythic-service-status-primary">
|
||||
<span className="mythic-service-status-dot" />
|
||||
<span className="mythic-service-status-primary-label">{label}</span>
|
||||
</div>
|
||||
{details.length > 0 &&
|
||||
<div className="mythic-service-status-details">
|
||||
{details.map((detail) => (
|
||||
<span
|
||||
className={`mythic-service-status-detail mythic-service-status-detail-${detail.tone || "neutral"}`.trim()}
|
||||
key={`${detail.label}-${detail.value}`}
|
||||
>
|
||||
<span className="mythic-service-status-mini-dot" />
|
||||
<span className="mythic-service-status-detail-label">{detail.label}</span>
|
||||
<span className="mythic-service-status-detail-value">{detail.value}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const InstalledServiceContainerStatus = ({isOnline}) => (
|
||||
<InstalledServiceStatusSummary
|
||||
label={isOnline ? "Container online" : "Container offline"}
|
||||
tone={isOnline ? "success" : "error"}
|
||||
/>
|
||||
);
|
||||
|
||||
const getC2ProfileStatusSummary = (service) => {
|
||||
if(!service.container_running){
|
||||
return {label: "Container offline", tone: "error"};
|
||||
}
|
||||
if(service.is_p2p){
|
||||
return {label: "Container online", tone: "success"};
|
||||
}
|
||||
if(service.running){
|
||||
return {label: "Accepting connections", tone: "success"};
|
||||
}
|
||||
return {label: "Server stopped", tone: "warning"};
|
||||
}
|
||||
|
||||
const getC2ProfileStatusDetails = (service) => {
|
||||
if(service.is_p2p){
|
||||
return [];
|
||||
}
|
||||
if(!service.container_running){
|
||||
return [{label: "Server", value: "unavailable", tone: "neutral"}];
|
||||
}
|
||||
return [{label: "Container", value: "online", tone: getServiceStatusTone(service.container_running, "neutral")}];
|
||||
}
|
||||
|
||||
export const C2ProfileStatusSummary = ({service}) => {
|
||||
const summary = getC2ProfileStatusSummary(service);
|
||||
return (
|
||||
<InstalledServiceStatusSummary
|
||||
label={summary.label}
|
||||
tone={summary.tone}
|
||||
details={getC2ProfileStatusDetails(service)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import {PayloadTypeCommandDialog} from "./PayloadTypeCommandsDialog";
|
||||
import {MythicAgentSVGIcon} from "../../MythicComponents/MythicAgentSVGIcon";
|
||||
import {C2ProfileListFilesDialog} from "./C2ProfileListFilesDialog";
|
||||
import AttachFileIcon from '@mui/icons-material/AttachFile';
|
||||
import {MythicStatusChip} from "../../MythicComponents/MythicStatusChip";
|
||||
import {InstalledServiceContainerStatus} from "./InstalledServiceStatus";
|
||||
|
||||
const toggleDeleteStatus = gql`
|
||||
mutation togglePayloadTypeDeleteStatus($payloadtype_id: Int!, $deleted: Boolean!){
|
||||
@@ -84,7 +84,10 @@ export function PayloadTypeRow({service, showDeleted}){
|
||||
<MythicAgentSVGIcon payload_type={service.name} style={{width: "80px", padding: "5px", objectFit: "unset"}} />
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{service.name}
|
||||
<div className="mythic-installed-service-identity">
|
||||
<span className="mythic-installed-service-name">{service.name}</span>
|
||||
<InstalledServiceContainerStatus isOnline={service.container_running} />
|
||||
</div>
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{service.wrapper ? "Wrapper" : service.agent_type === "agent" ? "Agent" : service.agent_type === "service" ? "3rd Party Service" : "Command Augmentation"}
|
||||
@@ -110,9 +113,6 @@ export function PayloadTypeRow({service, showDeleted}){
|
||||
<b>Description: </b>{service.note}
|
||||
</Typography>
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
<MythicStatusChip label={service.container_running ? "Online" : "Offline"} status={service.container_running ? "success" : "error"} />
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
<div className="mythic-table-row-actions">
|
||||
<MythicStyledTooltip title={"Documentation"}>
|
||||
|
||||
@@ -477,7 +477,6 @@ A few examples are github.com/MythicAgents/bloodhound and github.com/MythicAgent
|
||||
<MythicTableCell>Service</MythicTableCell>
|
||||
<MythicTableCell style={{width: "4rem"}}>Type</MythicTableCell>
|
||||
<MythicTableCell>Metadata</MythicTableCell>
|
||||
<MythicTableCell>Status</MythicTableCell>
|
||||
<MythicTableCell style={{width: "12rem"}}>Actions</MythicTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
+5
-5
@@ -15,7 +15,7 @@ import MythicTableCell from "../../MythicComponents/MythicTableCell";
|
||||
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
|
||||
import {MythicDialog} from "../../MythicComponents/MythicDialog";
|
||||
import {C2ProfileListFilesDialog} from "./C2ProfileListFilesDialog";
|
||||
import {MythicStatusChip} from "../../MythicComponents/MythicStatusChip";
|
||||
import {InstalledServiceContainerStatus} from "./InstalledServiceStatus";
|
||||
|
||||
const toggleDeleteStatus = gql`
|
||||
mutation toggleC2ProfileDeleteStatus($translationcontainer_id: Int!, $deleted: Boolean!){
|
||||
@@ -66,7 +66,10 @@ export function TranslationContainerRow({service, showDeleted}) {
|
||||
<FontAwesomeIcon icon={faLanguage} style={{width: "80px", height: "80px"}} />
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{service.name}
|
||||
<div className="mythic-installed-service-identity">
|
||||
<span className="mythic-installed-service-name">{service.name}</span>
|
||||
<InstalledServiceContainerStatus isOnline={service.container_running} />
|
||||
</div>
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
Translation
|
||||
@@ -82,9 +85,6 @@ export function TranslationContainerRow({service, showDeleted}) {
|
||||
<b>Description: </b>{service.description}
|
||||
</Typography>
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
<MythicStatusChip label={service.container_running ? "Online" : "Offline"} status={service.container_running ? "success" : "error"} />
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
<div className="mythic-table-row-actions">
|
||||
<MythicStyledTooltip title={"Documentation"}>
|
||||
|
||||
@@ -6119,6 +6119,7 @@ tspan {
|
||||
line-height: 1.25;
|
||||
}
|
||||
.mythic-eventing-detail {
|
||||
container-type: inline-size;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
@@ -6144,12 +6145,13 @@ tspan {
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
box-shadow: ${(props) => props.theme.palette.mode === "dark" ? "inset 0 1px 0 rgba(255,255,255,0.055)" : "inset 0 1px 0 rgba(255,255,255,0.78)"};
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
container-type: inline-size;
|
||||
display: grid;
|
||||
flex: 0 0 auto;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: minmax(10.5rem, 0.55fr) minmax(16rem, 0.82fr) minmax(31rem, 2.2fr);
|
||||
grid-template-columns: minmax(9.5rem, 0.55fr) minmax(13rem, 0.82fr) minmax(0, 2.2fr);
|
||||
min-width: 0;
|
||||
overflow: visible;
|
||||
overflow: hidden;
|
||||
padding: 0.72rem 0.78rem;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -6198,14 +6200,18 @@ tspan {
|
||||
.mythic-eventing-workflow-overview-header-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
flex: 1 1 auto;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
justify-content: flex-end;
|
||||
max-width: 100%;
|
||||
min-width: min(100%, 13.5rem);
|
||||
}
|
||||
.mythic-eventing-workflow-overview-header-actions .MuiButton-root.mythic-table-row-action {
|
||||
flex: 0 1 auto;
|
||||
min-width: 6.5rem;
|
||||
max-width: 100%;
|
||||
white-space: normal;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-section {
|
||||
display: flex;
|
||||
@@ -6294,10 +6300,16 @@ tspan {
|
||||
gap: 0.7rem;
|
||||
}
|
||||
.mythic-eventing-workflow-button-row .MuiButton-root.mythic-table-row-action {
|
||||
flex: 0 1 auto;
|
||||
gap: 0.25rem;
|
||||
justify-content: flex-start;
|
||||
line-height: 1.2;
|
||||
max-width: 100%;
|
||||
min-width: auto;
|
||||
padding-left: 0.55rem;
|
||||
padding-right: 0.65rem;
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
}
|
||||
.mythic-eventing-workflow-button-row .MuiButton-startIcon {
|
||||
margin-left: 0;
|
||||
@@ -6336,22 +6348,30 @@ tspan {
|
||||
background-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.28 : 0.18)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.68 : 0.5)} !important;
|
||||
}
|
||||
@media (max-width: 1180px) {
|
||||
@container (max-width: 58rem) {
|
||||
.mythic-eventing-workflow-overview {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.mythic-eventing-workflow-overview-header {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-header-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-section + .mythic-eventing-workflow-overview-section {
|
||||
border-left: 0;
|
||||
border-top: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
padding-left: 0;
|
||||
padding-top: 0.65rem;
|
||||
border-left: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-top: 0;
|
||||
padding-left: 0.75rem;
|
||||
padding-top: 0;
|
||||
}
|
||||
.mythic-eventing-workflow-overview-actions {
|
||||
border-left: 0 !important;
|
||||
border-top: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
grid-column: 1 / -1;
|
||||
padding-left: 0 !important;
|
||||
padding-top: 0.65rem !important;
|
||||
}
|
||||
}
|
||||
.mythic-eventing-instances-grid {
|
||||
@@ -6364,7 +6384,37 @@ tspan {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-eventing-instance-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-eventing-instance-id-cell {
|
||||
gap: 0.28rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.mythic-eventing-instance-id {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-weight: 850;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.MuiIconButton-root.mythic-eventing-instance-id-menu-button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.mythic-eventing-instance-status-cell .mythic-eventing-status-chip {
|
||||
max-width: 100%;
|
||||
}
|
||||
.mythic-eventing-instance-status-cell .mythic-eventing-status-chip span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-eventing-instances-time-cell {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.12rem;
|
||||
@@ -6876,16 +6926,23 @@ tspan {
|
||||
.mythic-eventing-code-block {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(0,0,0,0.24)" : "rgba(255,255,255,0.72)"};
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.42;
|
||||
margin: 0;
|
||||
max-height: 18rem;
|
||||
min-height: 3.4rem;
|
||||
overflow: auto;
|
||||
padding: 0.62rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
padding: 0;
|
||||
}
|
||||
.mythic-eventing-code-block .ace_editor {
|
||||
background-color: transparent !important;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;
|
||||
}
|
||||
.mythic-eventing-code-block .ace_gutter {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.035)"} !important;
|
||||
border-right: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
color: ${(props) => props.theme.palette.text.secondary} !important;
|
||||
}
|
||||
.mythic-eventing-code-block .ace_scroller {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
.mythic-eventing-code-block-empty {
|
||||
align-items: center;
|
||||
@@ -6894,6 +6951,7 @@ tspan {
|
||||
font-family: ${(props) => props.theme.typography.fontFamily};
|
||||
font-size: 0.74rem;
|
||||
font-weight: 700;
|
||||
padding: 0.62rem;
|
||||
}
|
||||
.mythic-eventing-detail-table-wrap {
|
||||
max-height: 18rem;
|
||||
@@ -6931,6 +6989,106 @@ tspan {
|
||||
.mythic-service-actions {
|
||||
gap: 0.55rem;
|
||||
}
|
||||
.mythic-installed-service-identity {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-installed-service-name {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-weight: 850;
|
||||
line-height: 1.25;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-service-status-summary {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.32rem;
|
||||
margin-top: 0.28rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-installed-service-identity .mythic-service-status-summary {
|
||||
margin-top: 0;
|
||||
}
|
||||
.mythic-service-status-primary {
|
||||
align-items: center;
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
display: flex;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 800;
|
||||
gap: 0.38rem;
|
||||
line-height: 1.25;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-service-status-primary-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-service-status-dot,
|
||||
.mythic-service-status-mini-dot {
|
||||
border-radius: 999px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.mythic-service-status-dot {
|
||||
height: 0.55rem;
|
||||
width: 0.55rem;
|
||||
}
|
||||
.mythic-service-status-mini-dot {
|
||||
height: 0.38rem;
|
||||
width: 0.38rem;
|
||||
}
|
||||
.mythic-service-status-summary-success .mythic-service-status-dot {
|
||||
background-color: ${(props) => props.theme.palette.success.main};
|
||||
box-shadow: 0 0 0 3px ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.14 : 0.1)};
|
||||
}
|
||||
.mythic-service-status-summary-warning .mythic-service-status-dot {
|
||||
background-color: ${(props) => props.theme.palette.warning.main};
|
||||
box-shadow: 0 0 0 3px ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.16 : 0.12)};
|
||||
}
|
||||
.mythic-service-status-summary-error .mythic-service-status-dot {
|
||||
background-color: ${(props) => props.theme.palette.error.main};
|
||||
box-shadow: 0 0 0 3px ${(props) => alpha(props.theme.palette.error.main, props.theme.palette.mode === "dark" ? 0.16 : 0.1)};
|
||||
}
|
||||
.mythic-service-status-details {
|
||||
align-items: center;
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.73rem;
|
||||
font-weight: 650;
|
||||
gap: 0.35rem 0.55rem;
|
||||
line-height: 1.3;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-service-status-detail {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
gap: 0.22rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-service-status-detail-label {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
}
|
||||
.mythic-service-status-detail-value {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-weight: 750;
|
||||
}
|
||||
.mythic-service-status-detail-neutral .mythic-service-status-mini-dot {
|
||||
background-color: ${(props) => alpha(props.theme.palette.text.secondary, 0.55)};
|
||||
}
|
||||
.mythic-service-status-detail-success .mythic-service-status-mini-dot {
|
||||
background-color: ${(props) => props.theme.palette.success.main};
|
||||
}
|
||||
.mythic-service-status-detail-warning .mythic-service-status-mini-dot {
|
||||
background-color: ${(props) => props.theme.palette.warning.main};
|
||||
}
|
||||
.mythic-service-status-detail-error .mythic-service-status-mini-dot {
|
||||
background-color: ${(props) => props.theme.palette.error.main};
|
||||
}
|
||||
.mythic-split-action-group {
|
||||
box-shadow: none !important;
|
||||
display: inline-flex;
|
||||
@@ -7044,9 +7202,27 @@ tspan {
|
||||
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-info .MuiSvgIcon-root,
|
||||
.mythic-menu-item-hover-success .MuiSvgIcon-root,
|
||||
.mythic-menu-item-hover-warning .MuiSvgIcon-root,
|
||||
.mythic-menu-item-hover-danger .MuiSvgIcon-root {
|
||||
color: inherit !important;
|
||||
}
|
||||
.mythic-menu-item-hover-info:hover,
|
||||
.mythic-menu-item-hover-info.Mui-focusVisible {
|
||||
background-color: ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)} !important;
|
||||
color: ${(props) => props.theme.palette.info.main} !important;
|
||||
}
|
||||
.mythic-menu-item-hover-success:hover,
|
||||
.mythic-menu-item-hover-success.Mui-focusVisible {
|
||||
background-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)} !important;
|
||||
color: ${(props) => props.theme.palette.success.main} !important;
|
||||
}
|
||||
.mythic-menu-item-hover-warning:hover,
|
||||
.mythic-menu-item-hover-warning.Mui-focusVisible {
|
||||
background-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.2 : 0.12)} !important;
|
||||
color: ${(props) => props.theme.palette.warning.main} !important;
|
||||
}
|
||||
.mythic-menu-item-hover-danger:hover,
|
||||
.mythic-menu-item-hover-danger.Mui-focusVisible {
|
||||
background-color: ${(props) => props.theme.palette.error.main + "16"} !important;
|
||||
|
||||
@@ -351,8 +351,18 @@ func CreateTask(createTaskInput CreateTaskInput) CreateTaskResponse {
|
||||
}
|
||||
}
|
||||
if task.Command.ID == 0 {
|
||||
logging.LogError(nil, "failed to find command matching payload type")
|
||||
response.Error = "failed to find command matching payload type"
|
||||
if createTaskInput.PayloadType != nil && *createTaskInput.PayloadType != "" {
|
||||
logging.LogError(nil, "explicit payload type provided", "payload type", *createTaskInput.PayloadType, "no loaded commands match that payload type and name")
|
||||
response.Error = fmt.Sprintf("This %s callback has no commands loaded that match the name (%s) and payload type (%s) provided.",
|
||||
callback.Payload.Payloadtype.Name,
|
||||
createTaskInput.CommandName,
|
||||
*createTaskInput.PayloadType)
|
||||
} else {
|
||||
logging.LogError(nil, "failed to find command for callback's payload type", "command", createTaskInput.CommandName, "payload type", callback.Payload.Payloadtype.Name)
|
||||
response.Error = fmt.Sprintf("This %s callback has no commands loaded that match the name %s",
|
||||
callback.Payload.Payloadtype.Name,
|
||||
createTaskInput.CommandName)
|
||||
}
|
||||
return response
|
||||
}
|
||||
task.CommandID.Int64 = int64(task.Command.ID)
|
||||
|
||||
Reference in New Issue
Block a user