mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
updated builder and eventing pages
This commit is contained in:
Generated
-39
@@ -21,7 +21,6 @@
|
||||
"@mui/material": "^7.0.2",
|
||||
"@mui/utils": "^7.0.2",
|
||||
"@mui/x-charts": "^8.1.0",
|
||||
"@mui/x-data-grid": "^8.1.0",
|
||||
"@mui/x-date-pickers": "^8.1.0",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
@@ -3845,44 +3844,6 @@
|
||||
"robust-predicates": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.1.0.tgz",
|
||||
"integrity": "sha512-jt64FTMRKg9xUOUQkHY+VDbgpLWGssgvTti9n2BNbA4wuOl9r0C44OzYsPMXR0BxgUoWWREUBaeA7x5uwaArnQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.0",
|
||||
"@mui/utils": "^7.0.2",
|
||||
"@mui/x-internals": "8.0.0",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^5.1.1",
|
||||
"use-sync-external-store": "^1.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-date-pickers": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.1.0.tgz",
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"@mui/material": "^7.0.2",
|
||||
"@mui/utils": "^7.0.2",
|
||||
"@mui/x-charts": "^8.1.0",
|
||||
"@mui/x-data-grid": "^8.1.0",
|
||||
"@mui/x-date-pickers": "^8.1.0",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
|
||||
@@ -586,90 +586,6 @@ const getModernThemeAdditions = (themeMode, preferences = operatorSettingDefault
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiDataGrid: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: backgroundPaper,
|
||||
border: `1px solid ${borderColor}`,
|
||||
borderRadius: 6,
|
||||
color: textPrimary,
|
||||
fontSize: "0.86rem",
|
||||
"--DataGrid-rowBorderColor": tableBorderSoft,
|
||||
"--DataGrid-containerBackground": tableHeaderColor,
|
||||
overflow: "hidden",
|
||||
"& .MuiDataGrid-main": {
|
||||
backgroundColor: backgroundPaper,
|
||||
},
|
||||
"& .MuiDataGrid-virtualScroller": {
|
||||
backgroundColor: backgroundPaper,
|
||||
},
|
||||
"& .MuiDataGrid-columnHeaders": {
|
||||
backgroundColor: tableHeaderColor,
|
||||
borderBottom: `1px solid ${borderColor}`,
|
||||
},
|
||||
"& .MuiDataGrid-columnHeader": {
|
||||
backgroundColor: tableHeaderColor,
|
||||
color: textPrimary,
|
||||
fontWeight: 700,
|
||||
},
|
||||
"& .MuiDataGrid-columnHeaderTitle": {
|
||||
fontSize: "0.76rem",
|
||||
fontWeight: 700,
|
||||
letterSpacing: 0,
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
"& .MuiDataGrid-columnSeparator": {
|
||||
color: tableBorderSoft,
|
||||
},
|
||||
"& .MuiDataGrid-cell": {
|
||||
borderBottom: `1px solid ${tableBorderSoft}`,
|
||||
outline: "none",
|
||||
fontVariantNumeric: "tabular-nums",
|
||||
},
|
||||
"& .MuiDataGrid-cell:focus, & .MuiDataGrid-cell:focus-within, & .MuiDataGrid-columnHeader:focus, & .MuiDataGrid-columnHeader:focus-within": {
|
||||
outline: "none",
|
||||
},
|
||||
"& .MuiDataGrid-row:nth-of-type(even):not(.Mui-selected)": {
|
||||
backgroundColor: tableRowStripeColor,
|
||||
},
|
||||
"& .MuiDataGrid-row:hover": {
|
||||
backgroundColor: tableRowHoverColor,
|
||||
},
|
||||
"& .MuiDataGrid-row.Mui-selected": {
|
||||
backgroundColor: tableSelectedColor,
|
||||
"&:hover": {
|
||||
backgroundColor: tableSelectedColor,
|
||||
},
|
||||
},
|
||||
"& .MuiDataGrid-footerContainer": {
|
||||
borderTop: `1px solid ${borderColor}`,
|
||||
minHeight: 34,
|
||||
color: textSecondary,
|
||||
},
|
||||
"& .MuiDataGrid-toolbarContainer": {
|
||||
borderBottom: `1px solid ${tableBorderSoft}`,
|
||||
minHeight: 34,
|
||||
},
|
||||
"& .MuiDataGrid-overlay": {
|
||||
backgroundColor: backgroundPaper,
|
||||
color: textSecondary,
|
||||
},
|
||||
"& .MuiTablePagination-root, & .MuiTablePagination-selectLabel, & .MuiTablePagination-displayedRows": {
|
||||
color: textSecondary,
|
||||
fontSize: "0.78rem",
|
||||
},
|
||||
"& .MuiSvgIcon-root, & .MuiIconButton-root": {
|
||||
color: textSecondary,
|
||||
},
|
||||
"& .MuiCheckbox-root": {
|
||||
color: textSecondary,
|
||||
"&.Mui-checked, &.MuiCheckbox-indeterminate": {
|
||||
color: primary,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import {MythicLoadingState, MythicSearchEmptyState} from "./MythicStateDisplay";
|
||||
|
||||
const DefaultNoRowsOverlay = () => (
|
||||
<MythicSearchEmptyState
|
||||
compact
|
||||
description="No rows match the current data or filters."
|
||||
minHeight={160}
|
||||
/>
|
||||
);
|
||||
|
||||
const DefaultLoadingOverlay = () => (
|
||||
<MythicLoadingState
|
||||
compact
|
||||
title="Loading rows"
|
||||
description="Fetching the latest table data."
|
||||
minHeight={160}
|
||||
/>
|
||||
);
|
||||
|
||||
export const MythicDataGrid = ({
|
||||
containerSx = {},
|
||||
sx = {},
|
||||
slots = {},
|
||||
density = "compact",
|
||||
autoPageSize = true,
|
||||
pageSizeOptions = [10, 25, 50, 100],
|
||||
columnHeaderHeight = 34,
|
||||
rowHeight = 34,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
minHeight: 0,
|
||||
overflow: "hidden",
|
||||
...containerSx,
|
||||
}}
|
||||
>
|
||||
<DataGrid
|
||||
density={density}
|
||||
autoPageSize={autoPageSize}
|
||||
pageSizeOptions={pageSizeOptions}
|
||||
columnHeaderHeight={columnHeaderHeight}
|
||||
rowHeight={rowHeight}
|
||||
hideFooterSelectedRowCount
|
||||
slots={{
|
||||
noRowsOverlay: DefaultNoRowsOverlay,
|
||||
loadingOverlay: DefaultLoadingOverlay,
|
||||
...slots,
|
||||
}}
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
minHeight: 0,
|
||||
width: "100%",
|
||||
...sx,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Skeleton from '@mui/material/Skeleton';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Typography from '@mui/material/Typography';
|
||||
@@ -124,3 +125,68 @@ export function MythicTableEmptyState({colSpan, ...props}) {
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export function MythicTableSearchEmptyState({colSpan, ...props}) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={colSpan} sx={{borderBottom: 0, p: 0}}>
|
||||
<MythicSearchEmptyState {...props} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export function MythicTableErrorState({colSpan, ...props}) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={colSpan} sx={{borderBottom: 0, p: 0}}>
|
||||
<MythicErrorState {...props} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export function MythicTableSkeletonRows({colSpan, columns = 4, rows = 4}) {
|
||||
const skeletonColumns = Math.max(1, columns);
|
||||
return (
|
||||
<>
|
||||
{[...Array(rows).keys()].map((rowIndex) => (
|
||||
<TableRow key={`mythic-table-skeleton-${rowIndex}`}>
|
||||
<TableCell colSpan={colSpan} sx={{px: 1.25, py: 0.75}}>
|
||||
<Box sx={{
|
||||
display: "grid",
|
||||
gap: 1,
|
||||
gridTemplateColumns: `repeat(${skeletonColumns}, minmax(0, 1fr))`,
|
||||
}}>
|
||||
{[...Array(skeletonColumns).keys()].map((columnIndex) => (
|
||||
<Skeleton
|
||||
animation="wave"
|
||||
height={22}
|
||||
key={`mythic-table-skeleton-${rowIndex}-${columnIndex}`}
|
||||
sx={{borderRadius: 1}}
|
||||
variant="rounded"
|
||||
width={columnIndex === skeletonColumns - 1 ? "72%" : "100%"}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function MythicTableLoadingState({colSpan, columns, rows = 4, showSkeleton = true, ...props}) {
|
||||
return (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableCell colSpan={colSpan} sx={{borderBottom: showSkeleton ? undefined : 0, p: 0}}>
|
||||
<MythicLoadingState {...props} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{showSkeleton &&
|
||||
<MythicTableSkeletonRows colSpan={colSpan} columns={columns} rows={rows} />
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ export const MythicTablePagination = ({
|
||||
fetchLimit,
|
||||
onChange,
|
||||
page,
|
||||
id,
|
||||
color = "primary",
|
||||
label = "Total Results",
|
||||
summary,
|
||||
className = "",
|
||||
containerStyle = {},
|
||||
paginationStyle = {},
|
||||
@@ -20,7 +22,7 @@ export const MythicTablePagination = ({
|
||||
const controlledPage = page === undefined ? {} : {page};
|
||||
|
||||
return (
|
||||
<Box className={`mythic-table-footer ${className}`.trim()} style={containerStyle}>
|
||||
<Box className={`mythic-table-footer ${className}`.trim()} id={id} style={containerStyle}>
|
||||
<Pagination
|
||||
count={pageCount}
|
||||
variant="outlined"
|
||||
@@ -34,9 +36,84 @@ export const MythicTablePagination = ({
|
||||
{...controlledPage}
|
||||
{...paginationProps}
|
||||
/>
|
||||
{totalCount !== undefined && (
|
||||
{summary !== undefined && summary !== null ? (
|
||||
<Typography className="mythic-table-total">{summary}</Typography>
|
||||
) : totalCount !== undefined && (
|
||||
<Typography className="mythic-table-total">{label}: {totalCount}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMythicClientPagination = ({
|
||||
items = [],
|
||||
resetKey = "",
|
||||
rowsPerPage = 25,
|
||||
}) => {
|
||||
const safeItems = React.useMemo(() => {
|
||||
return Array.isArray(items) ? items : [];
|
||||
}, [items]);
|
||||
const [page, setPage] = React.useState(1);
|
||||
const totalCount = safeItems.length;
|
||||
const pageCount = Math.max(1, Math.ceil(totalCount / rowsPerPage));
|
||||
const safePage = Math.min(page, pageCount);
|
||||
const pageStart = (safePage - 1) * rowsPerPage;
|
||||
const pageData = React.useMemo(() => {
|
||||
return safeItems.slice(pageStart, pageStart + rowsPerPage);
|
||||
}, [pageStart, rowsPerPage, safeItems]);
|
||||
const pageEnd = totalCount === 0 ? 0 : Math.min(pageStart + rowsPerPage, totalCount);
|
||||
const pageStartDisplay = totalCount === 0 ? 0 : pageStart + 1;
|
||||
|
||||
React.useEffect(() => {
|
||||
setPage(1);
|
||||
}, [resetKey, rowsPerPage, totalCount]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if(page > pageCount){
|
||||
setPage(pageCount);
|
||||
}
|
||||
}, [page, pageCount]);
|
||||
|
||||
const onChangePage = React.useCallback((event, nextPage) => {
|
||||
setPage(nextPage);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
onChangePage,
|
||||
page: safePage,
|
||||
pageCount,
|
||||
pageData,
|
||||
pageEnd,
|
||||
pageStart,
|
||||
pageStartDisplay,
|
||||
rangeLabel: `${pageStartDisplay}-${pageEnd} of ${totalCount}`,
|
||||
rowsPerPage,
|
||||
showPagination: totalCount > rowsPerPage,
|
||||
totalCount,
|
||||
};
|
||||
};
|
||||
|
||||
export const MythicClientSideTablePagination = ({
|
||||
id,
|
||||
pagination,
|
||||
selectedCount,
|
||||
selectedLabel = "selected",
|
||||
...paginationProps
|
||||
}) => {
|
||||
if(!pagination?.showPagination){
|
||||
return null;
|
||||
}
|
||||
const summary = selectedCount === undefined || selectedCount === null ?
|
||||
pagination.rangeLabel :
|
||||
`${pagination.rangeLabel}, ${selectedCount} ${selectedLabel}`;
|
||||
return (
|
||||
<MythicTablePagination
|
||||
count={pagination.pageCount}
|
||||
id={id}
|
||||
onChange={pagination.onChangePage}
|
||||
page={pagination.page}
|
||||
summary={summary}
|
||||
{...paginationProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,8 +3,8 @@ import { gql, useMutation, useSubscription } from '@apollo/client';
|
||||
import {BrowserScriptsTable} from './BrowserScriptsTable';
|
||||
import {snackActions} from '../../utilities/Snackbar';
|
||||
import { Backdrop } from '@mui/material';
|
||||
import {CircularProgress} from '@mui/material';
|
||||
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
|
||||
import {MythicLoadingState} from "../../MythicComponents/MythicStateDisplay";
|
||||
|
||||
|
||||
const SUB_BrowserScripts = gql`
|
||||
@@ -138,7 +138,7 @@ export function BrowserScripts({me}){
|
||||
return (
|
||||
<MythicPageBody>
|
||||
<Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute"}} invisible={false}>
|
||||
<CircularProgress color="inherit" />
|
||||
<MythicLoadingState compact title="Loading browser scripts" description="Fetching scripts for this operator." sx={{color: "inherit"}} />
|
||||
</Backdrop>
|
||||
<BrowserScriptsTable
|
||||
browserscripts={browserScripts} operation_id={me?.user?.current_operation_id || 0} onToggleActive={onToggleActive}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
@@ -6,9 +6,8 @@ import {useQuery, gql } from '@apollo/client';
|
||||
import {useMutation} from '@apollo/client';
|
||||
import {hideCallbacksMutation} from './CallbackMutations';
|
||||
import {snackActions} from "../../utilities/Snackbar";
|
||||
import {CallbacksTableLastCheckinCell, CallbacksTablePayloadTypeCell, CallbacksTableIPCell} from "./CallbacksTableRow";
|
||||
import {MythicDataGrid} from "../../MythicComponents/MythicDataGrid";
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import {CallbacksTabsSelectTable} from "./CallbacksTabsSelectTable";
|
||||
|
||||
|
||||
const callbacksAndFeaturesQuery = gql`
|
||||
@@ -35,106 +34,6 @@ query callbacksAndFeatures{
|
||||
}
|
||||
}`;
|
||||
|
||||
const columns = [
|
||||
{ field: 'display_id', headerName: 'ID', width: 70, type: 'number', },
|
||||
{
|
||||
field: 'host',
|
||||
headerName: 'Host',
|
||||
flex: 0.5,
|
||||
},
|
||||
{
|
||||
field: 'user',
|
||||
headerName: 'User',
|
||||
flex: 0.5,
|
||||
},
|
||||
{
|
||||
field: 'pid',
|
||||
headerName: 'PID',
|
||||
type: 'number',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
headerName: 'Description',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
headerName: 'IP',
|
||||
width: 100,
|
||||
renderCell: (params) => <CallbacksTableIPCell rowData={params.row} cellData={params.row.ip} />,
|
||||
sortable: false,
|
||||
valueGetter: (value, row) => {
|
||||
try{
|
||||
return JSON.parse(row.ip)[0];
|
||||
}catch(error){
|
||||
return row.ip;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: "last_checkin",
|
||||
headerName: "Checkin",
|
||||
width: 100,
|
||||
valueGetter: (value, row) => new Date(row.last_checkin),
|
||||
renderCell: (params) =>
|
||||
<CallbacksTableLastCheckinCell rowData={params.row} />,
|
||||
},
|
||||
{
|
||||
field: "payload.payloadtype.name",
|
||||
headerName: "Agent",
|
||||
flex: 0.5,
|
||||
valueGetter: (value, row) => row.payload.payloadtype.name,
|
||||
renderCell: (params) => <CallbacksTablePayloadTypeCell rowData={params.row} />
|
||||
},
|
||||
{
|
||||
field: "mythictree_groups_string",
|
||||
headerName: "Groups",
|
||||
flex: 0.5,
|
||||
}
|
||||
];
|
||||
const CustomSelectTable = ({initialData, selectedData, sortModel}) => {
|
||||
const [data, setData] = React.useState([]);
|
||||
const [rowSelectionModel, setRowSelectionModel] = React.useState({
|
||||
type: 'include',
|
||||
ids: new Set([]),
|
||||
});
|
||||
React.useEffect( () => {
|
||||
selectedData.current = data.reduce( (prev, cur) => {
|
||||
if(rowSelectionModel.ids.has(cur.id)){return [...prev, cur]}
|
||||
return [...prev];
|
||||
}, []);
|
||||
}, [data, rowSelectionModel]);
|
||||
React.useEffect( () => {
|
||||
setData(initialData.map(c => {
|
||||
return {...c};
|
||||
}));
|
||||
}, [initialData]);
|
||||
return (
|
||||
<div style={{height: "calc(80vh)", minHeight: 0}}>
|
||||
<MythicDataGrid
|
||||
rows={data}
|
||||
columns={columns}
|
||||
initialState={{
|
||||
pagination: {
|
||||
paginationModel: {
|
||||
},
|
||||
},
|
||||
sorting: {
|
||||
sortModel: [sortModel],
|
||||
},
|
||||
}}
|
||||
autoPageSize
|
||||
checkboxSelection
|
||||
onRowSelectionModelChange={(newRowSelectionModel) => {
|
||||
setRowSelectionModel(newRowSelectionModel);
|
||||
}}
|
||||
rowSelectionModel={rowSelectionModel}
|
||||
/>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
export function CallbacksTabsHideMultipleDialog({onClose}) {
|
||||
|
||||
const selectedData = React.useRef([]);
|
||||
@@ -150,7 +49,7 @@ export function CallbacksTabsHideMultipleDialog({onClose}) {
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
useQuery(callbacksAndFeaturesQuery,{
|
||||
const {loading} = useQuery(callbacksAndFeaturesQuery,{
|
||||
fetchPolicy: "no-cache",
|
||||
onCompleted: (data) => {
|
||||
const callbackData = data.callback.map( c => {
|
||||
@@ -178,9 +77,12 @@ export function CallbacksTabsHideMultipleDialog({onClose}) {
|
||||
<DialogContentText style={{textAlign: "center"}}>
|
||||
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
|
||||
</DialogContentText>
|
||||
<CustomSelectTable initialData={initialData}
|
||||
selectedData={selectedData}
|
||||
sortModel={{ field: 'last_checkin', sort: 'asc' }}
|
||||
<CallbacksTabsSelectTable initialData={initialData}
|
||||
loading={loading}
|
||||
selectedData={selectedData}
|
||||
sortModel={{ field: 'last_checkin', sort: 'asc' }}
|
||||
tableId="hide-multiple-callbacks-table"
|
||||
tableLabel="Hide multiple callbacks"
|
||||
/>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="contained" color="primary">
|
||||
@@ -197,7 +99,7 @@ export function CallbacksTabsSelectMultipleDialog({onClose, onSubmit}) {
|
||||
|
||||
const selectedData = React.useRef([]);
|
||||
const [initialData, setInitialData] = React.useState([]);
|
||||
useQuery(callbacksAndFeaturesQuery,{
|
||||
const {loading} = useQuery(callbacksAndFeaturesQuery,{
|
||||
fetchPolicy: "no-cache",
|
||||
onCompleted: (data) => {
|
||||
const callbackData = data.callback.map( c => {
|
||||
@@ -223,9 +125,12 @@ export function CallbacksTabsSelectMultipleDialog({onClose, onSubmit}) {
|
||||
<DialogContentText style={{textAlign: "center"}}>
|
||||
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
|
||||
</DialogContentText>
|
||||
<CustomSelectTable initialData={initialData}
|
||||
selectedData={selectedData}
|
||||
sortModel={{ field: 'display_id', sort: 'desc' }}
|
||||
<CallbacksTabsSelectTable initialData={initialData}
|
||||
loading={loading}
|
||||
selectedData={selectedData}
|
||||
sortModel={{ field: 'display_id', sort: 'desc' }}
|
||||
tableId="select-multiple-callbacks-table"
|
||||
tableLabel="Select multiple callbacks"
|
||||
/>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="contained" color="primary">
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import React, {useContext} from 'react';
|
||||
import React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import {useQuery, gql } from '@apollo/client';
|
||||
import {CallbacksTableLastCheckinCell, CallbacksTablePayloadTypeCell, CallbacksTableIPCell} from "./CallbacksTableRow";
|
||||
import {MythicDataGrid} from "../../MythicComponents/MythicDataGrid";
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import {CallbacksTabsSelectTable} from "./CallbacksTabsSelectTable";
|
||||
|
||||
|
||||
const callbacksAndFeaturesQuery = gql`
|
||||
@@ -32,96 +31,9 @@ query callbacksAndFeatures{
|
||||
}
|
||||
}`;
|
||||
|
||||
const columns = [
|
||||
{ field: 'display_id', headerName: 'ID', width: 70, type: 'number', },
|
||||
{
|
||||
field: 'host',
|
||||
headerName: 'Host',
|
||||
flex: 0.5,
|
||||
},
|
||||
{
|
||||
field: 'user',
|
||||
headerName: 'User',
|
||||
flex: 0.5,
|
||||
},
|
||||
{
|
||||
field: 'pid',
|
||||
headerName: 'PID',
|
||||
type: 'number',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
headerName: 'Description',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
headerName: 'IP',
|
||||
width: 100,
|
||||
renderCell: (params) => <CallbacksTableIPCell rowData={params.row} cellData={params.row.ip} />,
|
||||
sortable: false,
|
||||
valueGetter: (value, row) => {
|
||||
try{
|
||||
return JSON.parse(row.ip)[0];
|
||||
}catch(error){
|
||||
return row.ip;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: "last_checkin",
|
||||
headerName: "Checkin",
|
||||
width: 100,
|
||||
valueGetter: (value, row) => new Date(row.last_checkin),
|
||||
renderCell: (params) =>
|
||||
<CallbacksTableLastCheckinCell rowData={params.row} />,
|
||||
},
|
||||
{
|
||||
field: "payload.payloadtype.name",
|
||||
headerName: "Agent",
|
||||
flex: 0.5,
|
||||
valueGetter: (value, row) => row.payload.payloadtype.name,
|
||||
renderCell: (params) => <CallbacksTablePayloadTypeCell rowData={params.row} />
|
||||
},
|
||||
{
|
||||
field: "mythictree_groups_string",
|
||||
headerName: "Groups",
|
||||
flex: 0.5,
|
||||
}
|
||||
];
|
||||
const CustomSelectTable = ({initialData, selectedData, onRowClick}) => {
|
||||
const [data, setData] = React.useState([]);
|
||||
React.useEffect( () => {
|
||||
setData(initialData.map(c => {
|
||||
return {...c};
|
||||
}));
|
||||
}, [initialData]);
|
||||
return (
|
||||
<div style={{height: "calc(80vh)", minHeight: 0}}>
|
||||
<MythicDataGrid
|
||||
rows={data}
|
||||
columns={columns}
|
||||
initialState={{
|
||||
pagination: {
|
||||
paginationModel: {
|
||||
},
|
||||
},
|
||||
sorting: {
|
||||
sortModel: [{ field: 'display_id', sort: 'desc' }],
|
||||
},
|
||||
}}
|
||||
autoPageSize
|
||||
onRowClick={onRowClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
export function CallbacksTabsOpenMultipleDialog({onClose, tabType, onOpenTabs}) {
|
||||
const selectedData = React.useRef([]);
|
||||
const [initialData, setInitialData] = React.useState([]);
|
||||
useQuery(callbacksAndFeaturesQuery,{
|
||||
const {loading} = useQuery(callbacksAndFeaturesQuery,{
|
||||
fetchPolicy: "no-cache",
|
||||
onCompleted: (data) => {
|
||||
const callbackData = data.callback.map( c => {
|
||||
@@ -133,7 +45,7 @@ export function CallbacksTabsOpenMultipleDialog({onClose, tabType, onOpenTabs})
|
||||
}
|
||||
});
|
||||
const onRowClick = (rowData) => {
|
||||
onOpenTabs([rowData.row]);
|
||||
onOpenTabs([rowData]);
|
||||
}
|
||||
|
||||
|
||||
@@ -143,9 +55,13 @@ export function CallbacksTabsOpenMultipleDialog({onClose, tabType, onOpenTabs})
|
||||
<DialogContentText style={{textAlign: "center"}}>
|
||||
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
|
||||
</DialogContentText>
|
||||
<CustomSelectTable initialData={initialData}
|
||||
selectedData={selectedData}
|
||||
onRowClick={onRowClick}
|
||||
<CallbacksTabsSelectTable initialData={initialData}
|
||||
loading={loading}
|
||||
onRowClick={onRowClick}
|
||||
selectable={false}
|
||||
sortModel={{ field: 'display_id', sort: 'desc' }}
|
||||
tableId={`open-multiple-${tabType}-callbacks-table`}
|
||||
tableLabel={`Open ${tabType} callback`}
|
||||
/>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="contained" color="primary">
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
import React from 'react';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
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 TableRow from '@mui/material/TableRow';
|
||||
import TableSortLabel from '@mui/material/TableSortLabel';
|
||||
import MythicStyledTableCell from "../../MythicComponents/MythicTableCell";
|
||||
import {MythicTableEmptyState, MythicTableLoadingState} from "../../MythicComponents/MythicStateDisplay";
|
||||
import {CallbacksTableIPCell, CallbacksTableLastCheckinCell, CallbacksTablePayloadTypeCell} from "./CallbacksTableRow";
|
||||
import {MythicClientSideTablePagination, useMythicClientPagination} from "../../MythicComponents/MythicTablePagination";
|
||||
|
||||
const getPrimaryIP = (row) => {
|
||||
try{
|
||||
return JSON.parse(row.ip)[0] || "";
|
||||
}catch(error){
|
||||
return row.ip || "";
|
||||
}
|
||||
}
|
||||
|
||||
const compareValues = (left, right) => {
|
||||
if(left === right){
|
||||
return 0;
|
||||
}
|
||||
if(left === undefined || left === null){
|
||||
return -1;
|
||||
}
|
||||
if(right === undefined || right === null){
|
||||
return 1;
|
||||
}
|
||||
if(left instanceof Date && right instanceof Date){
|
||||
return left.getTime() - right.getTime();
|
||||
}
|
||||
if(typeof left === "number" && typeof right === "number"){
|
||||
return left - right;
|
||||
}
|
||||
return String(left).localeCompare(String(right), undefined, {numeric: true, sensitivity: "base"});
|
||||
}
|
||||
|
||||
const getColumns = ({includeAgent}) => {
|
||||
const columns = [
|
||||
{field: "display_id", headerName: "ID", width: "4.5rem", sortValue: (row) => row.display_id},
|
||||
{field: "host", headerName: "Host", sortValue: (row) => row.host},
|
||||
{field: "user", headerName: "User", sortValue: (row) => row.user},
|
||||
{field: "pid", headerName: "PID", width: "4.5rem", sortValue: (row) => row.pid},
|
||||
{field: "description", headerName: "Description", sortValue: (row) => row.description},
|
||||
{
|
||||
field: "ip",
|
||||
headerName: "IP",
|
||||
width: "8rem",
|
||||
sortValue: getPrimaryIP,
|
||||
render: (row) => <CallbacksTableIPCell rowData={row} cellData={row.ip} />,
|
||||
},
|
||||
{
|
||||
field: "last_checkin",
|
||||
headerName: "Checkin",
|
||||
width: "8rem",
|
||||
sortValue: (row) => new Date(row.last_checkin),
|
||||
render: (row) => <CallbacksTableLastCheckinCell rowData={row} />,
|
||||
},
|
||||
];
|
||||
if(includeAgent){
|
||||
columns.push({
|
||||
field: "payload.payloadtype.name",
|
||||
headerName: "Agent",
|
||||
sortValue: (row) => row.payload?.payloadtype?.name || "",
|
||||
render: (row) => <CallbacksTablePayloadTypeCell rowData={row} />,
|
||||
});
|
||||
}
|
||||
columns.push({field: "mythictree_groups_string", headerName: "Groups", sortValue: (row) => row.mythictree_groups_string});
|
||||
return columns;
|
||||
}
|
||||
|
||||
export const CallbacksTabsSelectTable = ({
|
||||
includeAgent = true,
|
||||
initialData,
|
||||
loading = false,
|
||||
onRowClick,
|
||||
selectable = true,
|
||||
selectedData,
|
||||
sortModel = {field: "display_id", sort: "desc"},
|
||||
tableId = "callbacks-select-table",
|
||||
tableLabel = "Callback selection",
|
||||
rowsPerPage = 25,
|
||||
}) => {
|
||||
const [data, setData] = React.useState([]);
|
||||
const [orderBy, setOrderBy] = React.useState(sortModel.field);
|
||||
const [order, setOrder] = React.useState(sortModel.sort || "asc");
|
||||
const [selectedIds, setSelectedIds] = React.useState(new Set());
|
||||
const columns = React.useMemo(() => getColumns({includeAgent}), [includeAgent]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setOrderBy(sortModel.field);
|
||||
setOrder(sortModel.sort || "asc");
|
||||
}, [sortModel.field, sortModel.sort]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setData(initialData.map((c) => ({...c})));
|
||||
setSelectedIds(new Set());
|
||||
}, [initialData]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if(selectable && selectedData){
|
||||
selectedData.current = data.filter((row) => selectedIds.has(row.id));
|
||||
}
|
||||
}, [data, selectable, selectedData, selectedIds]);
|
||||
|
||||
const sortedData = React.useMemo(() => {
|
||||
const sortColumn = columns.find((column) => column.field === orderBy);
|
||||
if(!sortColumn){
|
||||
return data;
|
||||
}
|
||||
const direction = order === "desc" ? -1 : 1;
|
||||
return [...data].sort((left, right) => direction * compareValues(sortColumn.sortValue(left), sortColumn.sortValue(right)));
|
||||
}, [columns, data, order, orderBy]);
|
||||
|
||||
const selectedCount = selectedIds.size;
|
||||
const allSelected = selectable && sortedData.length > 0 && selectedCount === sortedData.length;
|
||||
const partiallySelected = selectable && selectedCount > 0 && selectedCount < sortedData.length;
|
||||
const pagination = useMythicClientPagination({
|
||||
items: sortedData,
|
||||
resetKey: `${orderBy}:${order}`,
|
||||
rowsPerPage,
|
||||
});
|
||||
|
||||
const handleSort = (field) => {
|
||||
if(orderBy === field){
|
||||
setOrder((current) => current === "asc" ? "desc" : "asc");
|
||||
}else{
|
||||
setOrderBy(field);
|
||||
setOrder("asc");
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectAll = (event) => {
|
||||
if(event.target.checked){
|
||||
setSelectedIds(new Set(sortedData.map((row) => row.id)));
|
||||
}else{
|
||||
setSelectedIds(new Set());
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSelectedRow = (row) => {
|
||||
setSelectedIds((current) => {
|
||||
const next = new Set(current);
|
||||
if(next.has(row.id)){
|
||||
next.delete(row.id);
|
||||
}else{
|
||||
next.add(row.id);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
const handleSelectRow = (event, row) => {
|
||||
event.stopPropagation();
|
||||
toggleSelectedRow(row);
|
||||
}
|
||||
|
||||
const handleRowClick = (row) => {
|
||||
if(selectable){
|
||||
toggleSelectedRow(row);
|
||||
}else if(onRowClick){
|
||||
onRowClick(row);
|
||||
}
|
||||
}
|
||||
|
||||
const handleRowKeyDown = (event, row) => {
|
||||
if(!selectable && !onRowClick){
|
||||
return;
|
||||
}
|
||||
if(event.key === "Enter" || event.key === " "){
|
||||
event.preventDefault();
|
||||
handleRowClick(row);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer
|
||||
aria-label={`${tableLabel} scroll area`}
|
||||
className="mythicElement mythic-dialog-table-wrap mythic-fixed-row-table-wrap mythic-callback-select-table-wrap"
|
||||
data-testid={`${tableId}-scroll-region`}
|
||||
id={`${tableId}-scroll-region`}
|
||||
role="region"
|
||||
style={{height: "min(60vh, 42rem)", minHeight: "20rem", overflowY: "auto"}}
|
||||
>
|
||||
<Table aria-label={tableLabel} data-testid={tableId} id={tableId} stickyHeader size="small" style={{height: "auto"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{selectable &&
|
||||
<MythicStyledTableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={allSelected}
|
||||
color="primary"
|
||||
indeterminate={partiallySelected}
|
||||
inputProps={{"aria-label": `Select all callbacks in ${tableLabel}`}}
|
||||
onChange={handleSelectAll}
|
||||
size="small"
|
||||
/>
|
||||
</MythicStyledTableCell>
|
||||
}
|
||||
{columns.map((column) => (
|
||||
<MythicStyledTableCell key={column.field} sortDirection={orderBy === column.field ? order : false} style={{width: column.width}}>
|
||||
<TableSortLabel
|
||||
active={orderBy === column.field}
|
||||
direction={orderBy === column.field ? order : "asc"}
|
||||
onClick={() => handleSort(column.field)}
|
||||
>
|
||||
{column.headerName}
|
||||
</TableSortLabel>
|
||||
</MythicStyledTableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody id={`${tableId}-body`}>
|
||||
{loading ? (
|
||||
<MythicTableLoadingState
|
||||
colSpan={columns.length + (selectable ? 1 : 0)}
|
||||
columns={columns.length + (selectable ? 1 : 0)}
|
||||
compact
|
||||
rows={4}
|
||||
title="Loading callbacks"
|
||||
description="Fetching callback data."
|
||||
minHeight={100}
|
||||
/>
|
||||
) : sortedData.length === 0 ? (
|
||||
<MythicTableEmptyState
|
||||
colSpan={columns.length + (selectable ? 1 : 0)}
|
||||
compact
|
||||
title="No callbacks"
|
||||
description="No active callbacks match this selection."
|
||||
minHeight={180}
|
||||
/>
|
||||
) : (
|
||||
pagination.pageData.map((row) => {
|
||||
const selected = selectedIds.has(row.id);
|
||||
return (
|
||||
<TableRow
|
||||
aria-label={`Callback ${row.display_id} ${row.user || ""}@${row.host || ""}`}
|
||||
className={selected ? "selectedCallback" : ""}
|
||||
hover
|
||||
id={`${tableId}-row-${row.id}`}
|
||||
key={row.id}
|
||||
onClick={() => handleRowClick(row)}
|
||||
onKeyDown={(event) => handleRowKeyDown(event, row)}
|
||||
tabIndex={selectable || onRowClick ? 0 : undefined}
|
||||
selected={selected}
|
||||
style={{cursor: selectable || onRowClick ? "pointer" : undefined}}
|
||||
>
|
||||
{selectable &&
|
||||
<MythicStyledTableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={selected}
|
||||
color="primary"
|
||||
inputProps={{"aria-label": `Select callback ${row.display_id}`}}
|
||||
onClick={(event) => handleSelectRow(event, row)}
|
||||
size="small"
|
||||
/>
|
||||
</MythicStyledTableCell>
|
||||
}
|
||||
{columns.map((column) => (
|
||||
<MythicStyledTableCell key={column.field}>
|
||||
{column.render ? column.render(row) : row[column.field]}
|
||||
</MythicStyledTableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{!loading &&
|
||||
<MythicClientSideTablePagination
|
||||
id={`${tableId}-pagination`}
|
||||
pagination={pagination}
|
||||
selectedCount={selectable ? selectedCount : undefined}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
@@ -6,11 +6,10 @@ import Grid from '@mui/material/Grid';
|
||||
import {useQuery, gql } from '@apollo/client';
|
||||
import {TaskFromUIButton} from './TaskFromUIButton';
|
||||
import {CallbacksTabsTaskingInput} from "./CallbacksTabsTaskingInput";
|
||||
import {CallbacksTableIPCell, CallbacksTableLastCheckinCell} from "./CallbacksTableRow";
|
||||
import {MythicDataGrid} from "../../MythicComponents/MythicDataGrid";
|
||||
import { validate as uuidValidate } from 'uuid';
|
||||
import {snackActions} from "../../utilities/Snackbar";
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import {CallbacksTabsSelectTable} from "./CallbacksTabsSelectTable";
|
||||
|
||||
|
||||
const callbacksAndFeaturesQuery = gql`
|
||||
@@ -36,99 +35,6 @@ query callbacksAndFeatures($payloadtype_id: Int!) {
|
||||
}
|
||||
}`;
|
||||
|
||||
const columns = [
|
||||
{ field: 'display_id', headerName: 'ID', width: 80, type: 'number', },
|
||||
{
|
||||
field: 'host',
|
||||
headerName: 'Host',
|
||||
flex: 0.5,
|
||||
},
|
||||
{
|
||||
field: 'user',
|
||||
headerName: 'User',
|
||||
flex: 0.5,
|
||||
},
|
||||
{
|
||||
field: 'pid',
|
||||
headerName: 'PID',
|
||||
type: 'number',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
headerName: 'Description',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
headerName: 'IP',
|
||||
width: 100,
|
||||
renderCell: (params) => <CallbacksTableIPCell rowData={params.row} cellData={params.row.ip} />,
|
||||
sortable: false,
|
||||
valueGetter: (value, row) => {
|
||||
try{
|
||||
return JSON.parse(row.ip)[0];
|
||||
}catch(error){
|
||||
return row.ip;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: "last_checkin",
|
||||
headerName: "Checkin",
|
||||
width: 100,
|
||||
valueGetter: (value, row) => new Date(row.last_checkin),
|
||||
renderCell: (params) =>
|
||||
<CallbacksTableLastCheckinCell rowData={params.row} />,
|
||||
},
|
||||
{
|
||||
field: "mythictree_groups_string",
|
||||
headerName: "Groups",
|
||||
flex: 0.5,
|
||||
}
|
||||
];
|
||||
const CustomSelectTable = ({initialData, selectedData}) => {
|
||||
const [data, setData] = React.useState([]);
|
||||
const [rowSelectionModel, setRowSelectionModel] = React.useState({
|
||||
type: 'include',
|
||||
ids: new Set([]),
|
||||
});
|
||||
React.useEffect( () => {
|
||||
selectedData.current = data.reduce( (prev, cur) => {
|
||||
if(rowSelectionModel.ids.has(cur.id)){return [...prev, cur]}
|
||||
return [...prev];
|
||||
}, []);
|
||||
}, [data, rowSelectionModel]);
|
||||
React.useEffect( () => {
|
||||
setData(initialData.map(c => {
|
||||
return {...c};
|
||||
}));
|
||||
}, [initialData]);
|
||||
return (
|
||||
<div style={{height: "calc(80vh)", minHeight: 0}}>
|
||||
<MythicDataGrid
|
||||
rows={data}
|
||||
columns={columns}
|
||||
initialState={{
|
||||
pagination: {
|
||||
paginationModel: {
|
||||
},
|
||||
},
|
||||
sorting: {
|
||||
sortModel: [{ field: 'display_id', sort: 'desc' }],
|
||||
},
|
||||
}}
|
||||
autoPageSize
|
||||
checkboxSelection
|
||||
onRowSelectionModelChange={(newRowSelectionModel) => {
|
||||
setRowSelectionModel(newRowSelectionModel);
|
||||
}}
|
||||
rowSelectionModel={rowSelectionModel}
|
||||
/>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
export function CallbacksTabsTaskMultipleDialog({onClose, callback}) {
|
||||
const mountedRef = React.useRef(true);
|
||||
const [selectedToken, setSelectedToken] = React.useState({});
|
||||
@@ -137,7 +43,7 @@ export function CallbacksTabsTaskMultipleDialog({onClose, callback}) {
|
||||
const finalTaskedParameters = React.useRef(null);
|
||||
const [initialData, setInitialData] = React.useState([]);
|
||||
const selectedData = React.useRef([]);
|
||||
useQuery(callbacksAndFeaturesQuery, {variables: {payloadtype_id: callback.payload.payloadtype.id},
|
||||
const {loading} = useQuery(callbacksAndFeaturesQuery, {variables: {payloadtype_id: callback.payload.payloadtype.id},
|
||||
fetchPolicy: "no-cache",
|
||||
onCompleted: (data) => {
|
||||
setInitialData(data.callback.map( c => {
|
||||
@@ -294,8 +200,15 @@ export function CallbacksTabsTaskMultipleDialog({onClose, callback}) {
|
||||
<DialogContentText style={{textAlign: "center"}}>
|
||||
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
|
||||
</DialogContentText>
|
||||
<CustomSelectTable initialData={initialData}
|
||||
selectedData={selectedData} />
|
||||
<CallbacksTabsSelectTable
|
||||
includeAgent={false}
|
||||
initialData={initialData}
|
||||
loading={loading}
|
||||
selectedData={selectedData}
|
||||
sortModel={{ field: 'display_id', sort: 'desc' }}
|
||||
tableId="task-multiple-callbacks-table"
|
||||
tableLabel={`Task multiple ${callback.payload.payloadtype.name} callbacks`}
|
||||
/>
|
||||
<Grid size={12}>
|
||||
<CallbacksTabsTaskingInput filterTasks={false} onSubmitFilter={()=>{}} onSubmitCommandLine={onSubmitCommandLine}
|
||||
changeSelectedToken={changeSelectedToken}
|
||||
|
||||
+17
-35
@@ -1,54 +1,36 @@
|
||||
import React from 'react';
|
||||
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 TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { CreatePayloadParameter } from './CreatePayloadParameter';
|
||||
import {GetGroupedParameters} from "./Step1SelectOS";
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
|
||||
|
||||
export function CreatePayloadBuildParametersTable(props){
|
||||
const theme = useTheme();
|
||||
const buildParameters = GetGroupedParameters({
|
||||
buildParameters: props.buildParameters,
|
||||
os: props.os,
|
||||
c2_name: props.c2_name,
|
||||
});
|
||||
return (
|
||||
<div style={{height: "100%", overflowY: "auto", width: "100%"}}>
|
||||
<div className="mythic-create-parameter-scroll">
|
||||
{buildParameters.map(b => (
|
||||
b.parameters.length > 0 &&
|
||||
<span key={b?.name || 'undefined'}>
|
||||
<section className="mythic-create-parameter-group" key={b?.name || 'undefined'}>
|
||||
{b.name !== '' && b.name !== undefined &&
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main,marginBottom: "5px", }} variant={"elevation"}>
|
||||
<Typography variant="h6" style={{textAlign: "left", display: "inline-block", marginLeft: "20px", color: theme.pageHeaderColor}}>
|
||||
{b.name}
|
||||
</Typography>
|
||||
</Paper>
|
||||
<div className="mythic-create-parameter-group-header">{b.name}</div>
|
||||
}
|
||||
<TableContainer className="mythicElement">
|
||||
<Table stickyHeader={true} size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "scroll"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "30%"}}>Build Parameter</TableCell>
|
||||
<TableCell>Value</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{b.parameters.map( (op) => (
|
||||
<CreatePayloadParameter selected_os={props.os} key={"buildparamtablerow" + op.id}
|
||||
payload_type={props.payload_type}
|
||||
instance_name={props.instance_name} onChange={props.onChange} {...op} />
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</span>
|
||||
<div className="mythic-create-parameter-list">
|
||||
{b.parameters.map( (op) => (
|
||||
<CreatePayloadParameter
|
||||
displayMode="card"
|
||||
selected_os={props.os}
|
||||
key={"buildparamtablerow" + op.id}
|
||||
payload_type={props.payload_type}
|
||||
instance_name={props.instance_name}
|
||||
onChange={props.onChange}
|
||||
{...op}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
+11
-21
@@ -1,11 +1,5 @@
|
||||
import React from 'react';
|
||||
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 TableRow from '@mui/material/TableRow';
|
||||
import { CreatePayloadParameter } from './CreatePayloadParameter';
|
||||
import MythicStyledTableCell from "../../MythicComponents/MythicTableCell";
|
||||
|
||||
|
||||
export function CreatePayloadC2ProfileParametersTable(props){
|
||||
@@ -14,20 +8,16 @@ export function CreatePayloadC2ProfileParametersTable(props){
|
||||
props.onChange(props.name, paramName, value, error);
|
||||
}
|
||||
return (
|
||||
<TableContainer className="mythicElement">
|
||||
<Table size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "auto"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell style={{width: "20%"}}>Parameter</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Value</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.c2profileparameters.map( (op) => (
|
||||
<CreatePayloadParameter key={"c2paramtablerow" + op.id} returnAllDictValues={props.returnAllDictValues} onChange={onChange} {...op} />
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<div className="mythic-create-parameter-list">
|
||||
{props.c2profileparameters.map( (op) => (
|
||||
<CreatePayloadParameter
|
||||
displayMode="card"
|
||||
key={"c2paramtablerow" + op.id}
|
||||
returnAllDictValues={props.returnAllDictValues}
|
||||
onChange={onChange}
|
||||
{...op}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ function isTrue(value){
|
||||
}
|
||||
export function CreatePayloadParameter({onChange, parameter_type, default_value, name, required, verifier_regex, id,
|
||||
description, initialValue, choices, trackedValue, instance_name,
|
||||
payload_type, selected_os, dynamic_query_function}){
|
||||
payload_type, selected_os, dynamic_query_function, displayMode = "table"}){
|
||||
const theme = useTheme();
|
||||
const [value, setValue] = React.useState("");
|
||||
const [valueNum, setValueNum] = React.useState(0);
|
||||
@@ -487,11 +487,11 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
)
|
||||
case "ChooseOne":
|
||||
return (
|
||||
<div style={{position: "relative", display: "flex", alignItems: "center", overflow: "hidden"}}>
|
||||
<div className="mythic-create-inline-control">
|
||||
<Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute"}} invisible={false}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Backdrop>
|
||||
<FormControl style={{width: "100%"}}>
|
||||
<FormControl>
|
||||
{ChoiceOptions.length === 0 &&
|
||||
<InputLabel>{"No Options Available"}</InputLabel>
|
||||
}
|
||||
@@ -521,8 +521,8 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
<Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute"}} invisible={false}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Backdrop>
|
||||
<div style={{width: "100%", display: "flex", alignItems: "center"}}>
|
||||
<FormControl style={{width: "20%"}}>
|
||||
<div className="mythic-create-inline-control">
|
||||
<FormControl style={{flex: "0 1 12rem"}}>
|
||||
<Select
|
||||
multiple={false}
|
||||
disabled={chooseOneCustomValue !== ""}
|
||||
@@ -537,7 +537,7 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
}
|
||||
</Select>
|
||||
</FormControl>
|
||||
OR
|
||||
<span className="mythic-create-choice-divider">OR</span>
|
||||
<MythicTextField name={name} requiredValue={required} placeholder={"Custom Value"} value={chooseOneCustomValue} multiline={true} maxRows={5}
|
||||
onChange={onChangeTextChooseOneCustom} display="inline-block"
|
||||
/>
|
||||
@@ -554,11 +554,11 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
)
|
||||
case "ChooseMultiple":
|
||||
return (
|
||||
<div style={{position: "relative", display: "flex", alignItems: "center", overflow: "hidden"}}>
|
||||
<div className="mythic-create-inline-control">
|
||||
<Backdrop open={backdropOpen} style={{zIndex: 2, position: "absolute"}} invisible={false}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Backdrop>
|
||||
<FormControl style={{width: "100%"}}>
|
||||
<FormControl>
|
||||
<InputLabel id={name + "select"} style={{paddingTop: "15px"}}>{"Select Multiple"}</InputLabel>
|
||||
{ChoiceOptions.length === 0 &&
|
||||
<InputLabel>{"No Options Available"}</InputLabel>
|
||||
@@ -586,7 +586,7 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
);
|
||||
case "Array":
|
||||
return (
|
||||
<TableContainer className="mythicElement">
|
||||
<TableContainer className="mythicElement mythic-create-array-table">
|
||||
<Table size="small" style={{tableLayout: "fixed", maxWidth: "100%", "overflow": "auto"}}>
|
||||
<TableBody>
|
||||
{arrayValue.map( (a, i) => (
|
||||
@@ -616,7 +616,7 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
);
|
||||
case "TypedArray":
|
||||
return (
|
||||
<TableContainer className="mythicElement">
|
||||
<TableContainer className="mythicElement mythic-create-array-table">
|
||||
<Table size="small" style={{tableLayout: "fixed", maxWidth: "100%", "overflow": "auto"}}>
|
||||
<TableBody>
|
||||
{typedArrayValue.map( (a, i) => (
|
||||
@@ -662,15 +662,15 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
return (
|
||||
<React.Fragment>
|
||||
{dictValue.map( (opt, i) => (
|
||||
<div key={"dictval" + i}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-danger" style={{width: "5%"}} onClick={(e) => {removeDictEntry(i)}} size="small"><DeleteIcon fontSize="small" /> </IconButton>
|
||||
<Input style={{width:"20%"}} startAdornment={<Button disabled>Key</Button>} size="small" value={opt.name} onChange={(e) => onChangeDictKey(e, i)}></Input>
|
||||
<Input style={{width:"75%"}} startAdornment={<Button disabled>value</Button>} size="small" value={opt.value} onChange={(e) => onChangeDictVal(e, i)}></Input>
|
||||
<div className="mythic-create-dictionary-row" key={"dictval" + i}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-danger" onClick={(e) => {removeDictEntry(i)}} size="small"><DeleteIcon fontSize="small" /> </IconButton>
|
||||
<Input startAdornment={<Button disabled>Key</Button>} size="small" value={opt.name} onChange={(e) => onChangeDictKey(e, i)}></Input>
|
||||
<Input startAdornment={<Button disabled>Value</Button>} size="small" value={opt.value} onChange={(e) => onChangeDictVal(e, i)}></Input>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{dictSelectOptions.length > 0 ? (
|
||||
<div>
|
||||
<div className="mythic-create-dictionary-add">
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-success" size="small" onClick={addDictValEntry}> <AddCircleIcon fontSize="small" /> </IconButton>
|
||||
<Select size="small" value={dictSelectOptionsChoice} onChange={(e) => setDictSelectOptionsChoice(e.target.value)}>
|
||||
{dictSelectOptions.map( (selectOpt, i) => (
|
||||
@@ -796,7 +796,33 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const isModified = modifiedValue();
|
||||
if(displayMode === "card"){
|
||||
return (
|
||||
<div className={`mythic-create-parameter-card ${isModified ? "mythic-create-parameter-card-modified" : ""}`.trim()} key={"buildparam" + id}>
|
||||
<div className="mythic-create-parameter-copy">
|
||||
<div className="mythic-create-parameter-title-row">
|
||||
<Typography component="div" className="mythic-create-parameter-title">
|
||||
{name}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="mythic-create-parameter-chips">
|
||||
<span className="mythic-create-parameter-chip">{parameter_type}</span>
|
||||
{required && <span className="mythic-create-parameter-chip mythic-create-parameter-chip-required">Required</span>}
|
||||
{isModified && <span className="mythic-create-parameter-chip mythic-create-parameter-chip-modified">Modified</span>}
|
||||
</div>
|
||||
{description &&
|
||||
<Typography component="div" className="mythic-create-parameter-description">
|
||||
{description}
|
||||
</Typography>
|
||||
}
|
||||
</div>
|
||||
<div className="mythic-create-parameter-control">
|
||||
{getParameterObject()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<TableRow key={"buildparam" + id} hover>
|
||||
<MythicStyledTableCell>
|
||||
@@ -808,7 +834,7 @@ export function CreatePayloadParameter({onChange, parameter_type, default_value,
|
||||
{description}
|
||||
</Typography>
|
||||
|
||||
{modifiedValue() ? (
|
||||
{isModified ? (
|
||||
<Typography color="warning.main">Modified</Typography>
|
||||
) : null}
|
||||
</MythicStyledTooltip>
|
||||
|
||||
@@ -13,10 +13,9 @@ import AddCircleIcon from '@mui/icons-material/AddCircle';
|
||||
import {getDefaultChoices, getDefaultValueForType, getSavedToType} from "./Step2SelectPayloadType";
|
||||
import {CreatePayloadBuildParametersTable} from "./CreatePayloadBuildParametersTable";
|
||||
import {ParseForDisplay} from "../Payloads/DetailedPayloadTable";
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import {getModifiedC2Params} from "./Step4C2Profiles";
|
||||
import { Backdrop, CircularProgress } from '@mui/material';
|
||||
import { Backdrop } from '@mui/material';
|
||||
import {MythicLoadingState} from "../../MythicComponents/MythicStateDisplay";
|
||||
|
||||
|
||||
const GET_Payload_Types = gql`
|
||||
@@ -414,31 +413,23 @@ export function Step1SelectOS(props){
|
||||
payloadConfigRef.current = payload
|
||||
}
|
||||
return (
|
||||
<div style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}>
|
||||
{/* Content area that can grow */}
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
minHeight: 0 // Important for flex shrinking
|
||||
}}>
|
||||
{/* Top section - fixed height */}
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexShrink: 0 // Don't shrink this section
|
||||
}}>
|
||||
<div style={{width: "100%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px"}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
1. Select Operating System
|
||||
</Typography>
|
||||
<div className="mythic-create-flow-shell">
|
||||
<div className="mythic-create-flow-content">
|
||||
<div className="mythic-create-selection-grid">
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Select operating system
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Filter payload types and C2 profiles by the target platform.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Select
|
||||
className="mythic-create-select"
|
||||
value={os}
|
||||
style={{width: "100%"}}
|
||||
disabled={!props.first}
|
||||
onChange={onChangeOS}
|
||||
>
|
||||
@@ -448,85 +439,87 @@ export function Step1SelectOS(props){
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
<Typography style={{fontWeight: 600}}>
|
||||
Compatible Payload Types
|
||||
</Typography>
|
||||
{payloadtypesPerOS[os]?.join(", ")}
|
||||
<Typography style={{fontWeight: 600}}>
|
||||
Compatible C2 Profiles
|
||||
</Typography>
|
||||
{C2PerOS[os]?.join(", ")}
|
||||
</div>
|
||||
<div style={{width: "100%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px"}}>
|
||||
<div style={{width: "100%", display: "flex", alignItems: "flex-start", marginBottom: "10px", flexDirection: "column"}}>
|
||||
<Typography style={{fontWeight: 600}} variant={"p"}>
|
||||
2. Select Payload Type
|
||||
</Typography>
|
||||
<Select
|
||||
style={{width: "100%"}}
|
||||
disabled={!props.first}
|
||||
value={selectedPayloadType}
|
||||
onChange={evt => setSelectedPayloadType(evt.target.value)}
|
||||
>
|
||||
{
|
||||
payloadtypesPerOS[os]?.map((opt) => (
|
||||
<MenuItem key={"step1" + opt} value={opt}>{opt}</MenuItem>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
|
||||
<div className="mythic-create-meta-list">
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Compatible payload types</span>
|
||||
<div className="mythic-create-meta-value">{payloadtypesPerOS[os]?.join(", ")}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Compatible C2 profiles</span>
|
||||
<div className="mythic-create-meta-value">{C2PerOS[os]?.join(", ")}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{display: "flex"}}>
|
||||
<MythicAgentSVGIcon payload_type={selectedPayloadType} style={{width: "80px", padding: "5px", objectFit: "unset"}} />
|
||||
<Typography variant="body2" component="p" style={{whiteSpace: "pre-wrap"}}>
|
||||
<b>Version: </b>{payloadtypeData[selectedPayloadType]?.semver}<br/>
|
||||
<b>Description: </b>{payloadtypeData[selectedPayloadType]?.note}<br/>
|
||||
<b>C2: </b>{payloadtypeData[selectedPayloadType]?.payloadtypec2profiles.map(c => c.c2profile.name).join(", ")}
|
||||
</Typography>
|
||||
</section>
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Select payload type
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Choose the agent family to configure for this build.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<Select
|
||||
className="mythic-create-select"
|
||||
disabled={!props.first}
|
||||
value={selectedPayloadType}
|
||||
onChange={evt => setSelectedPayloadType(evt.target.value)}
|
||||
>
|
||||
{
|
||||
payloadtypesPerOS[os]?.map((opt) => (
|
||||
<MenuItem key={"step1" + opt} value={opt}>{opt}</MenuItem>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
<div className="mythic-create-agent-summary">
|
||||
<div className="mythic-create-agent-icon">
|
||||
<MythicAgentSVGIcon payload_type={selectedPayloadType} style={{width: "100%", height: "100%", objectFit: "contain"}} />
|
||||
</div>
|
||||
<div className="mythic-create-meta-list">
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Version</span>
|
||||
<div className="mythic-create-meta-value">{payloadtypeData[selectedPayloadType]?.semver || "Unknown"}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Description</span>
|
||||
<div className="mythic-create-meta-value">{payloadtypeData[selectedPayloadType]?.note || "No description available."}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">C2</span>
|
||||
<div className="mythic-create-meta-value">{payloadtypeData[selectedPayloadType]?.payloadtypec2profiles.map(c => c.c2profile.name).join(", ") || "None"}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Bottom section - scrollable table area */}
|
||||
<div style={{
|
||||
margin: "5px",
|
||||
border: props.first ? "1px solid grey" : '',
|
||||
borderRadius: "5px",
|
||||
padding: "10px 5px 5px 10px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{props.first &&
|
||||
<div style={{flexGrow: 1, overflowY: "auto", position: "relative"}}>
|
||||
<section className="mythic-create-section mythic-create-section-fill">
|
||||
{props.first ? (
|
||||
<div style={{display: "flex", flexDirection: "column", flexGrow: 1, minHeight: 0, overflow: "hidden", position: "relative"}}>
|
||||
{openBackdrop &&
|
||||
<Backdrop open={openBackdrop} onClick={()=>{setOpenBackdrop(false);}} style={{zIndex: 2000, position: "absolute"}}>
|
||||
<CircularProgress color="inherit" disableShrink />
|
||||
<Backdrop open={openBackdrop} style={{zIndex: 2000, position: "absolute"}}>
|
||||
<MythicLoadingState compact title="Loading payloads" description="Fetching compatible payloads." sx={{color: "inherit"}} />
|
||||
</Backdrop>
|
||||
}
|
||||
<StartFromExistingPayloadOrStartFresh first={props.first}
|
||||
last={props.last}
|
||||
canceled={canceled}
|
||||
onSelectedPayload={onSelectedPayload}
|
||||
payloadOptions={payloadOptions}
|
||||
onStartFresh={onStartFresh}
|
||||
/>
|
||||
{!openBackdrop &&
|
||||
<StartFromExistingPayloadOrStartFresh first={props.first}
|
||||
last={props.last}
|
||||
canceled={canceled}
|
||||
onSelectedPayload={onSelectedPayload}
|
||||
payloadOptions={payloadOptions}
|
||||
onStartFresh={onStartFresh}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
{!props.first &&
|
||||
) : (
|
||||
<ConfigureBuildParameters os={os} selectedPayloadType={selectedPayloadType}
|
||||
prevData={props.prevData}
|
||||
onUpdatePayloadConfig={onUpdatePayloadConfig} />
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Navigation buttons - always at bottom */}
|
||||
<div style={{flexShrink: 0}}>
|
||||
<div className="mythic-create-flow-footer">
|
||||
<CreatePayloadNavigationButtons
|
||||
first={props.first}
|
||||
last={props.last}
|
||||
@@ -544,27 +537,32 @@ export const StartFromExistingPayloadOrStartFresh = (
|
||||
) => {
|
||||
return (
|
||||
<>
|
||||
{/* Header section - fixed */}
|
||||
<div style={{flexShrink: 0}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
3. Continue from Existing Payload or
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Continue from existing payload
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Select a compatible payload to reuse its configuration, or start with defaults.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="mythic-create-section-actions">
|
||||
<Button
|
||||
className="mythic-table-row-action mythic-table-row-action-hover-success"
|
||||
size="small"
|
||||
variant="contained"
|
||||
style={{marginLeft: "10px", marginBottom: "5px"}}
|
||||
onClick={onStartFresh}
|
||||
startIcon={<AddCircleIcon fontSize="small" />} >
|
||||
Start Fresh
|
||||
</Button>
|
||||
</Typography>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scrollable table container */}
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflow: "hidden",
|
||||
minHeight: 0 // Important for flex shrinking
|
||||
}}>
|
||||
<PayloadSelect
|
||||
@@ -675,51 +673,27 @@ export const ConfigureBuildParameters = (
|
||||
}, [selectedPayloadType, prevData?.payload]);
|
||||
return (
|
||||
<>
|
||||
{/* Header section - fixed */}
|
||||
<div style={{flexShrink: 0}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
3.5. Configure Payload Build Parameters
|
||||
</Typography>
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Configure payload build parameters
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Review defaults, required fields, and any values changed from the payload type baseline.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
minHeight: 0 // Important for flex shrinking
|
||||
}}>
|
||||
<div style={{
|
||||
width: "30%",
|
||||
margin: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
padding: "10px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "auto"
|
||||
}}>
|
||||
<Typography textAlign="center" variant={"h7"} style={{fontWeight: 600, width: "100%"}}>
|
||||
<div className="mythic-create-builder-split">
|
||||
<section className="mythic-create-section mythic-create-section-scroll">
|
||||
<Typography component="div" className="mythic-create-section-title" style={{textAlign: "center"}}>
|
||||
Configuration Summary
|
||||
</Typography>
|
||||
<ConfigurationSummary buildParameters={payloadTypeParameters} os={os} />
|
||||
</div>
|
||||
<div style={{
|
||||
width: "100%",
|
||||
margin: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
padding: "0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
</section>
|
||||
<section className="mythic-create-section mythic-create-section-scroll">
|
||||
<CreatePayloadBuildParametersTable onChange={onChange} buildParameters={payloadTypeParameters} os={os}
|
||||
payload_type={selectedPayloadType}/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@@ -867,28 +841,25 @@ export const GetGroupedParameters = ({buildParameters, os, c2_name}) => {
|
||||
return groupedData;
|
||||
}
|
||||
export const ConfigurationSummary = ({buildParameters, os, c2_name}) => {
|
||||
const theme = useTheme();
|
||||
const [groupedParameters, setGroupedParameters] = React.useState([]);
|
||||
React.useEffect( () => {
|
||||
// grouped should be array of groupName
|
||||
setGroupedParameters(GetGroupedParameters({buildParameters, os, c2_name}));
|
||||
}, [buildParameters, c2_name]);
|
||||
return (
|
||||
groupedParameters?.map((b,i) => (
|
||||
<div key={b.name} >
|
||||
groupedParameters?.map((b) => (
|
||||
<div className="mythic-create-summary-group" key={b.name || "default-configuration-group"} >
|
||||
{b.name !== '' && b.name !== undefined && b.parameters.length > 0 &&
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main,marginBottom: "5px", }} variant={"elevation"}>
|
||||
<Typography variant="h6" style={{textAlign: "left", display: "inline-block", marginLeft: "20px", color: theme.pageHeaderColor}}>
|
||||
{b.name}
|
||||
</Typography>
|
||||
</Paper>
|
||||
<div className="mythic-create-summary-group-header">
|
||||
{b.name}
|
||||
</div>
|
||||
}
|
||||
{b?.parameters?.map( (p,i) => (
|
||||
<div key={p.name} style={{marginLeft: b.name === '' ? '' : "20px"}} className={i%2 > 0 ? 'alternateRow' : ''}>
|
||||
<Typography style={{fontWeight: 600}} variant={"body2"}>
|
||||
{b?.parameters?.map( (p) => (
|
||||
<div className="mythic-create-summary-row" key={p.name}>
|
||||
<Typography component="div" className="mythic-create-summary-name">
|
||||
{p.name}
|
||||
</Typography>
|
||||
<div style={{marginLeft: "20px", marginTop: "5px", marginBottom: "5px", whiteSpace: "pre"}}>
|
||||
<div className="mythic-create-summary-value">
|
||||
<ParseForDisplay cmd={p} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -141,144 +141,116 @@ export function Step3SelectCommands(props){
|
||||
const updateSelectedCommands = (commands) => {
|
||||
selectedCommands.current = commands;
|
||||
}
|
||||
const hasHoveredCommand = hoveredCommand?.cmd !== undefined;
|
||||
const commandReason = hoveredCommand?.reason || "This command can be included or removed as needed";
|
||||
return (
|
||||
<div style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}>
|
||||
{/* Content area that can grow */}
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
minHeight: 0 // Important for flex shrinking
|
||||
}}>
|
||||
{/* Left section - fixed height */}
|
||||
<div style={{
|
||||
padding: "5px",
|
||||
display: "flex",
|
||||
width: "60%",
|
||||
flexDirection: "column",
|
||||
minHeight: 0,
|
||||
overflow: "hidden" // Don't shrink this section
|
||||
}}>
|
||||
<div style={{width: "100%", marginBottom: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px", display: "flex", flexShrink: 0}}>
|
||||
<MythicAgentSVGIcon payload_type={props.buildOptions.payload_type} style={{width: "80px", padding: "5px", objectFit: "unset"}} />
|
||||
<Typography variant="body2" component="p" style={{whiteSpace: "pre-wrap"}}>
|
||||
<b>Description: </b>{props.buildOptions.description}
|
||||
</Typography>
|
||||
</div>
|
||||
<div style={{width: "100%",
|
||||
//border: "1px solid grey",
|
||||
flexDirection: "column",
|
||||
borderRadius: "5px",
|
||||
//padding: "10px",
|
||||
flexGrow: 1, minHeight: 0, display: "flex"}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
{"1. Select Commands to Include in the Payload"}
|
||||
</Typography>
|
||||
<div style={{flexGrow: 1, minHeight: 0, overflow: "hidden", display: "flex"}}>
|
||||
<CommandTransferSelect commands={commandOptions}
|
||||
payload_type={props.buildOptions["payload_type"]}
|
||||
first={props.first} last={props.last}
|
||||
updateSelectedCommands={updateSelectedCommands}
|
||||
setHoveredCommand={setHoveredCommand}/>
|
||||
<div className="mythic-create-flow-shell">
|
||||
<div className="mythic-create-flow-content">
|
||||
<div className="mythic-create-builder-split" style={{gridTemplateColumns: "minmax(0, 0.6fr) minmax(18rem, 0.4fr)"}}>
|
||||
<section className="mythic-create-section mythic-create-section-fill mythic-create-section-plain">
|
||||
<div className="mythic-create-subsection" style={{flex: "0 0 auto"}}>
|
||||
<div className="mythic-create-agent-summary">
|
||||
<div className="mythic-create-agent-icon">
|
||||
<MythicAgentSVGIcon payload_type={props.buildOptions.payload_type} style={{width: "100%", height: "100%", objectFit: "contain"}} />
|
||||
</div>
|
||||
<div className="mythic-create-meta-list">
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Selected payload type</span>
|
||||
<div className="mythic-create-meta-value">{props.buildOptions.payload_type}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Description</span>
|
||||
<div className="mythic-create-meta-value">{props.buildOptions.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mythic-create-subsection mythic-create-subsection-fill">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Select commands
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Move commands into the payload and review hover details before continuing.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{flexGrow: 1, minHeight: 0, overflow: "hidden", display: "flex"}}>
|
||||
<CommandTransferSelect commands={commandOptions}
|
||||
payload_type={props.buildOptions["payload_type"]}
|
||||
first={props.first} last={props.last}
|
||||
updateSelectedCommands={updateSelectedCommands}
|
||||
setHoveredCommand={setHoveredCommand}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Bottom section - scrollable table area */}
|
||||
<div style={{
|
||||
width: "40%",
|
||||
padding: "5px",
|
||||
borderRadius: "5px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
<div style={{width: "100%", flexGrow: 1, marginBottom: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px",
|
||||
height: "40%"}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
{"Hovered Command Details"}
|
||||
</Typography><br/>
|
||||
{hoveredCommand["cmd"] !== undefined &&
|
||||
<>
|
||||
{hoveredCommand["cmd"]}
|
||||
<StyledDivider classes={{root: classes.divider}}/>
|
||||
<Typography style={{}}>
|
||||
<b>Description: </b>
|
||||
<section className="mythic-create-section mythic-create-section-fill mythic-create-section-plain">
|
||||
<div className="mythic-create-subsection">
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Hovered command details
|
||||
</Typography>
|
||||
{hasHoveredCommand ? (
|
||||
<div className="mythic-create-meta-list">
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Command</span>
|
||||
<div className="mythic-create-meta-value">{hoveredCommand.cmd}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Description</span>
|
||||
<div className="mythic-create-meta-value">{hoveredCommand.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Hover over a command to preview its description and behavior.
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
<div className="mythic-create-subsection mythic-create-subsection-fill">
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Command behavior
|
||||
</Typography>
|
||||
<div className="mythic-create-subsection-scroll">
|
||||
{hasHoveredCommand ? (
|
||||
<div className="mythic-create-meta-list">
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">{hoveredCommand.disabled ? "Cannot be moved" : "Information"}</span>
|
||||
<div className="mythic-create-meta-value">{commandReason}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Command line help</span>
|
||||
<div className="mythic-create-meta-value">{hoveredCommand.help_cmd}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Needs admin permissions</span>
|
||||
<div className="mythic-create-meta-value">{hoveredCommand.needs_admin ? "True" : "False"}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Supported UI features</span>
|
||||
<div className="mythic-create-meta-value">{hoveredCommand?.supported_ui_features?.join(", ") || "None"}</div>
|
||||
</div>
|
||||
{hoveredCommand?.attributes?.dependencies && hoveredCommand?.attributes?.dependencies.length > 0 &&
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Dependencies</span>
|
||||
<div className="mythic-create-meta-value">{hoveredCommand?.attributes?.dependencies.join(", ")}</div>
|
||||
</div>}
|
||||
{hoveredCommand?.attributes?.alias !== undefined &&
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Alias</span>
|
||||
<div className="mythic-create-meta-value">{hoveredCommand?.attributes?.alias ? "True":"False"}</div>
|
||||
</div>}
|
||||
</div>
|
||||
) : (
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Command metadata appears here after hovering over an available or selected command.
|
||||
</Typography>
|
||||
<Typography style={{marginLeft: "20px"}}>
|
||||
{hoveredCommand["description"]}
|
||||
</Typography><br/>
|
||||
</>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div style={{width: "100%", flexGrow: 1, marginBottom: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px",
|
||||
height: "20%", overflow: "scroll"}}>
|
||||
{hoveredCommand["reason"] !== "" ? (
|
||||
<>
|
||||
<Typography style={{}}>
|
||||
<b>{hoveredCommand["disabled"] ? ("Cannot be moved: ") : ("Information: ")} </b>
|
||||
</Typography>
|
||||
<Typography style={{marginLeft: "20px"}}>
|
||||
{hoveredCommand["reason"]}
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Typography style={{}}>
|
||||
<b>{"Information: "} </b>
|
||||
</Typography>
|
||||
<Typography style={{marginLeft: "20px"}}>
|
||||
{"This command can be included or removed as needed"}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
<Typography style={{}}>
|
||||
<b>Command line Help: </b>
|
||||
</Typography>
|
||||
<Typography style={{marginLeft: "20px"}}>
|
||||
{hoveredCommand["help_cmd"]}
|
||||
</Typography>
|
||||
<Typography style={{}}>
|
||||
<b>Needs Admin Permissions: </b>
|
||||
</Typography>
|
||||
<Typography style={{marginLeft: "20px"}}>
|
||||
{hoveredCommand["needs_admin"] ? "True" : "False"}
|
||||
</Typography>
|
||||
<Typography style={{}}>
|
||||
<b>Supported UI Features: </b>
|
||||
</Typography>
|
||||
<Typography style={{marginLeft: "20px"}}>
|
||||
{hoveredCommand?.supported_ui_features?.join(", ")}
|
||||
</Typography>
|
||||
{hoveredCommand?.attributes?.dependencies && hoveredCommand?.attributes?.dependencies.length > 0 &&
|
||||
<>
|
||||
<Typography style={{}}>
|
||||
<b>Dependencies: </b>
|
||||
</Typography>
|
||||
<Typography style={{marginLeft: "20px"}}>
|
||||
{hoveredCommand?.attributes?.dependencies.join(", ")}
|
||||
</Typography>
|
||||
</>}
|
||||
{hoveredCommand?.attributes?.alias !== undefined &&
|
||||
<>
|
||||
<Typography style={{}}>
|
||||
<b>Alias: </b>
|
||||
</Typography>
|
||||
<Typography style={{marginLeft: "20px"}}>
|
||||
{hoveredCommand?.attributes?.alias ? "True":"False"}
|
||||
</Typography>
|
||||
</>}
|
||||
</div>
|
||||
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{openConfirmDialog &&
|
||||
@@ -288,8 +260,7 @@ export function Step3SelectCommands(props){
|
||||
acceptText="Accept"
|
||||
onSubmit={acceptConfirm} />
|
||||
}
|
||||
{/* Navigation buttons - always at bottom */}
|
||||
<div style={{flexShrink: 0}}>
|
||||
<div className="mythic-create-flow-footer">
|
||||
<CreatePayloadNavigationButtons
|
||||
first={props.first}
|
||||
last={props.last}
|
||||
|
||||
@@ -374,44 +374,39 @@ export function Step4C2Profiles(props){
|
||||
setDisabledC2Add(false);
|
||||
}, [includedC2Profiles, selectedC2]);
|
||||
return (
|
||||
<div style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}>
|
||||
{/* Content area that can grow */}
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
minHeight: 0 // Important for flex shrinking
|
||||
}}>
|
||||
{/* Top section - fixed height */}
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexShrink: 0 // Don't shrink this section
|
||||
}}>
|
||||
<div style={{width: "40%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px", display: "flex"}}>
|
||||
<MythicAgentSVGIcon payload_type={props.buildOptions.payload_type} style={{width: "80px", padding: "5px", objectFit: "unset"}} />
|
||||
<div>
|
||||
<Typography variant={"p"} style={{}}>
|
||||
<b>OS: </b>{props.buildOptions.os}
|
||||
</Typography><br/>
|
||||
<Typography variant="body2" component="p" style={{whiteSpace: "pre-wrap"}}>
|
||||
<b>Description: </b>{props.buildOptions.description}
|
||||
</Typography>
|
||||
<div className="mythic-create-flow-shell">
|
||||
<div className="mythic-create-flow-content">
|
||||
<div className="mythic-create-selection-grid">
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-agent-summary">
|
||||
<div className="mythic-create-agent-icon">
|
||||
<MythicAgentSVGIcon payload_type={props.buildOptions.payload_type} style={{width: "100%", height: "100%", objectFit: "contain"}} />
|
||||
</div>
|
||||
<div className="mythic-create-meta-list">
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Operating system</span>
|
||||
<div className="mythic-create-meta-value">{props.buildOptions.os}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Description</span>
|
||||
<div className="mythic-create-meta-value">{props.buildOptions.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Select C2 profiles
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Add one or more egress or peer profiles to configure for this payload.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{width: "60%",
|
||||
margin: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "5px", padding: "10px"}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
1. Select C2 Profiles to Include
|
||||
</Typography>
|
||||
<Select
|
||||
style={{width: "100%", marginBottom: "5px", marginTop: "5px", display: "flex"}}
|
||||
className="mythic-create-select"
|
||||
value={selectedC2}
|
||||
onChange={onChangeSelectedC2}
|
||||
>
|
||||
@@ -429,79 +424,43 @@ export function Step4C2Profiles(props){
|
||||
className="mythic-table-row-action mythic-table-row-action-hover-success"
|
||||
size="small"
|
||||
variant="contained"
|
||||
style={{marginLeft: "10px", marginBottom: "5px"}}
|
||||
onClick={addC2}
|
||||
disabled={disabledC2Add}
|
||||
startIcon={<AddCircleIcon fontSize="small" />} >
|
||||
Include Profile
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Bottom section - scrollable table area */}
|
||||
<div style={{
|
||||
margin: "5px",
|
||||
//border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
//padding: "10px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{/* Header section - fixed */}
|
||||
<div style={{flexShrink: 0}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
2. Include and Configure C2 Profiles
|
||||
</Typography>
|
||||
<section className="mythic-create-section mythic-create-section-fill">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Include and configure C2 profiles
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Review the active C2 profiles and adjust their build-time parameters.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
minHeight: 0 // Important for flex shrinking
|
||||
}}>
|
||||
<div style={{
|
||||
width: "30%",
|
||||
margin: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
padding: "5px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "auto"
|
||||
}}>
|
||||
<Typography textAlign="center" variant={"h7"} style={{fontWeight: 600, width: "100%"}}>
|
||||
<div className="mythic-create-builder-split">
|
||||
<section className="mythic-create-section mythic-create-section-scroll">
|
||||
<Typography component="div" className="mythic-create-section-title" style={{textAlign: "center"}}>
|
||||
Configuration Summary
|
||||
</Typography>
|
||||
{includedC2Profiles.map( (c, index) => (
|
||||
<ConfigurationSummary key={c.name + index} buildParameters={c.c2profileparameters}
|
||||
os={props.buildOptions.os} c2_name={c.name} />
|
||||
))}
|
||||
</div>
|
||||
<div style={{
|
||||
width: "100%",
|
||||
margin: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
padding: "0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
</section>
|
||||
<section className="mythic-create-section mythic-create-section-scroll">
|
||||
<C2ProfileTabs includedC2Profiles={includedC2Profiles} os={props.buildOptions.os}
|
||||
onCloseTab={removeC2} onChange={updateC2Parameter}
|
||||
onChangeCreatedInstanceName={onChangeCreatedInstanceName}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{openConfirmDialog &&
|
||||
<MythicConfirmDialog open={openConfirmDialog}
|
||||
@@ -510,8 +469,7 @@ export function Step4C2Profiles(props){
|
||||
acceptText="Accept"
|
||||
onSubmit={acceptConfirm} />
|
||||
}
|
||||
{/* Navigation buttons - always at bottom */}
|
||||
<div style={{flexShrink: 0}}>
|
||||
<div className="mythic-create-flow-footer">
|
||||
<CreatePayloadNavigationButtons
|
||||
first={props.first}
|
||||
last={props.last}
|
||||
@@ -673,7 +631,8 @@ const C2ProfileTabs = ({includedC2Profiles, onChange, os, onCloseTab, onChangeCr
|
||||
}} >
|
||||
{c.c2profileparametersinstances.length > 0 &&
|
||||
<Select
|
||||
style={{width: "100%", marginBottom: "5px"}}
|
||||
className="mythic-create-select"
|
||||
style={{marginBottom: "0.65rem"}}
|
||||
value={c.selected_instance}
|
||||
onChange={evt => onChangeCreatedInstanceName(evt, index, c)}
|
||||
>
|
||||
|
||||
@@ -202,78 +202,55 @@ export function Step5Build(props){
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}>
|
||||
{/* Content area that can grow */}
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
minHeight: 0 // Important for flex shrinking
|
||||
}}>
|
||||
{/* Top section - fixed height */}
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexShrink: 0 // Don't shrink this section
|
||||
}}>
|
||||
<div style={{width: "100%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px", display: "flex"}}>
|
||||
<MythicAgentSVGIcon payload_type={props.buildOptions[1].payload_type} style={{width: "80px", padding: "5px", objectFit: "unset"}} />
|
||||
<div>
|
||||
<Typography variant={"p"} style={{}}>
|
||||
<b>OS: </b>{props.buildOptions[1].os}
|
||||
<div className="mythic-create-flow-shell">
|
||||
<div className="mythic-create-flow-content">
|
||||
<div className="mythic-create-selection-grid">
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-agent-summary">
|
||||
<div className="mythic-create-agent-icon">
|
||||
<MythicAgentSVGIcon payload_type={props.buildOptions[1].payload_type} style={{width: "100%", height: "100%", objectFit: "contain"}} />
|
||||
</div>
|
||||
<div className="mythic-create-meta-list">
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Operating system</span>
|
||||
<div className="mythic-create-meta-value">{props.buildOptions[1].os}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Description</span>
|
||||
<div className="mythic-create-meta-value">{props.buildOptions[1].description}</div>
|
||||
</div>
|
||||
<MythicStyledTooltip title={"Edit OS / Payload Type"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => props.moveToStep(0)}>
|
||||
<DriveFileRenameOutlineIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</Typography><br/>
|
||||
<Typography variant="body2" component="p" style={{whiteSpace: "pre-wrap"}}>
|
||||
<b>Description: </b>{props.buildOptions[1].description}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{width: "100%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px"}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
1. Provide Payload Name and Description
|
||||
</Typography>
|
||||
<div style={{width: "100%", display: "flex", alignItems: "flex-start", marginBottom: "10px", flexDirection: "column"}}>
|
||||
</section>
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Payload name and description
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
These values are used for the generated file and operator-facing description.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mythic-form">
|
||||
<MythicTextField onEnter={finished} autoFocus={true} required={false} placeholder={"Filename"}
|
||||
value={filename} multiline={false} onChange={onChangeFilename} display="inline-block"/>
|
||||
<MythicTextField onEnter={finished} required={false} placeholder={"description"} value={description}
|
||||
multiline={false} onChange={onChangeDescription} display="inline-block"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Bottom section - scrollable table area */}
|
||||
<div style={{
|
||||
margin: "5px",
|
||||
// border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
//padding: "10px 5px 5px 10px",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
<div style={{
|
||||
width: "30%",
|
||||
margin: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
padding: "5px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "auto"
|
||||
}}>
|
||||
<Typography textAlign="center" variant={"h7"} style={{fontWeight: 600, width: "100%"}}>
|
||||
2. Build Parameter Configuration
|
||||
<div className="mythic-create-builder-split mythic-create-builder-split-three">
|
||||
<section className="mythic-create-section mythic-create-section-scroll">
|
||||
<Typography component="div" className="mythic-create-section-title" style={{textAlign: "center"}}>
|
||||
Build parameter configuration
|
||||
<MythicStyledTooltip title={"Edit Build Parameters"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => props.moveToStep(1)}>
|
||||
<DriveFileRenameOutlineIcon />
|
||||
@@ -281,21 +258,10 @@ export function Step5Build(props){
|
||||
</MythicStyledTooltip>
|
||||
</Typography>
|
||||
<ConfigurationSummary buildParameters={props.buildOptions[1].parameters} os={props.buildOptions[1].os} />
|
||||
</div>
|
||||
<div style={{
|
||||
width: "30%",
|
||||
margin: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
padding: "5px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "auto"
|
||||
}}>
|
||||
<Typography textAlign="center" variant={"h7"} style={{fontWeight: 600, width: "100%"}}>
|
||||
3. Command Selection
|
||||
</section>
|
||||
<section className="mythic-create-section mythic-create-section-scroll">
|
||||
<Typography component="div" className="mythic-create-section-title" style={{textAlign: "center"}}>
|
||||
Command selection
|
||||
<MythicStyledTooltip title={"Edit Commands"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => props.moveToStep(2)}>
|
||||
<DriveFileRenameOutlineIcon />
|
||||
@@ -313,21 +279,10 @@ export function Step5Build(props){
|
||||
</div>
|
||||
))}
|
||||
|
||||
</div>
|
||||
<div style={{
|
||||
width: "40%",
|
||||
margin: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
padding: "5px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "auto"
|
||||
}}>
|
||||
<Typography textAlign="center" variant={"h7"} style={{fontWeight: 600, width: "100%"}}>
|
||||
4. C2 Configuration
|
||||
</section>
|
||||
<section className="mythic-create-section mythic-create-section-scroll">
|
||||
<Typography component="div" className="mythic-create-section-title" style={{textAlign: "center"}}>
|
||||
C2 configuration
|
||||
<MythicStyledTooltip title={"Edit C2 Parameters"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => props.moveToStep(3)}>
|
||||
<DriveFileRenameOutlineIcon />
|
||||
@@ -338,12 +293,11 @@ export function Step5Build(props){
|
||||
<ConfigurationSummary key={c.name + index} buildParameters={c.c2profileparameters}
|
||||
os={props.buildOptions[1].os} c2_name={c.name} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation buttons - always at bottom */}
|
||||
<div style={{flexShrink: 0}}>
|
||||
<div className="mythic-create-flow-footer">
|
||||
<CreatePayloadNavigationButtons
|
||||
first={props.first}
|
||||
last={props.last}
|
||||
|
||||
+5
-21
@@ -1,30 +1,14 @@
|
||||
import React from 'react';
|
||||
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 TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import {CreatePayloadParameter} from '../CreatePayload/CreatePayloadParameter';
|
||||
|
||||
|
||||
export function CreatePayloadBuildParametersTable(props){
|
||||
|
||||
return (
|
||||
<TableContainer className="mythicElement">
|
||||
<Table size="small" style={{"tableLayout": "fixed", "maxWidth": "calc(100vw)", "overflow": "scroll"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "30%"}}>Build Parameter</TableCell>
|
||||
<TableCell>Value</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.buildParameters.map( (op) => (
|
||||
<CreatePayloadParameter key={"buildparamtablerow" + op.id} onChange={props.onChange} {...op} />
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<div className="mythic-create-parameter-list">
|
||||
{props.buildParameters.map( (op) => (
|
||||
<CreatePayloadParameter displayMode="card" key={"buildparamtablerow" + op.id} onChange={props.onChange} {...op} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ import Typography from '@mui/material/Typography';
|
||||
import {snackActions} from '../../utilities/Snackbar';
|
||||
import {useMythicLazyQuery} from "../../utilities/useMythicLazyQuery";
|
||||
import {getDefaultChoices, getDefaultValueForType, getSavedToType} from "../CreatePayload/Step2SelectPayloadType";
|
||||
import { Backdrop, CircularProgress } from '@mui/material';
|
||||
import { Backdrop } from '@mui/material';
|
||||
import {MythicAgentSVGIcon} from "../../MythicComponents/MythicAgentSVGIcon";
|
||||
import {MythicLoadingState} from "../../MythicComponents/MythicStateDisplay";
|
||||
import {
|
||||
ConfigureBuildParameters,
|
||||
GetPayloads,
|
||||
@@ -220,31 +221,23 @@ export function Step1SelectOS(props){
|
||||
payloadConfigRef.current = payload
|
||||
}
|
||||
return (
|
||||
<div style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}>
|
||||
{/* Content area that can grow */}
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
minHeight: 0 // Important for flex shrinking
|
||||
}}>
|
||||
{/* Top section - fixed height */}
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexShrink: 0 // Don't shrink this section
|
||||
}}>
|
||||
<div style={{width: "100%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px"}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
1. Select Operating System
|
||||
</Typography>
|
||||
<div className="mythic-create-flow-shell">
|
||||
<div className="mythic-create-flow-content">
|
||||
<div className="mythic-create-selection-grid">
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Select operating system
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Filter wrapper types by the target platform.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Select
|
||||
className="mythic-create-select"
|
||||
value={os}
|
||||
style={{width: "100%"}}
|
||||
disabled={!props.first}
|
||||
onChange={onChangeOS}
|
||||
>
|
||||
@@ -254,57 +247,57 @@ export function Step1SelectOS(props){
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
<Typography style={{fontWeight: 600}}>
|
||||
Compatible Payload Types
|
||||
</Typography>
|
||||
{payloadtypesPerOS[os]?.join(", ")}
|
||||
</div>
|
||||
<div style={{width: "100%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px"}}>
|
||||
<div style={{width: "100%", display: "flex", alignItems: "flex-start", marginBottom: "10px", flexDirection: "column"}}>
|
||||
<Typography style={{fontWeight: 600}} variant={"p"}>
|
||||
2. Select Payload Type
|
||||
</Typography>
|
||||
<Select
|
||||
style={{width: "100%"}}
|
||||
disabled={!props.first}
|
||||
value={selectedPayloadType}
|
||||
onChange={evt => setSelectedPayloadType(evt.target.value)}
|
||||
>
|
||||
{
|
||||
payloadtypesPerOS[os]?.map((opt) => (
|
||||
<MenuItem key={"step1" + opt} value={opt}>{opt}</MenuItem>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Compatible wrapper types</span>
|
||||
<div className="mythic-create-meta-value">{payloadtypesPerOS[os]?.join(", ")}</div>
|
||||
</div>
|
||||
<div style={{display: "flex"}}>
|
||||
<MythicAgentSVGIcon payload_type={selectedPayloadType} style={{width: "80px", padding: "5px", objectFit: "unset"}} />
|
||||
<Typography variant="body2" component="p" style={{whiteSpace: "pre-wrap"}}>
|
||||
<b>Version: </b>{payloadtypeData[selectedPayloadType]?.semver}<br/>
|
||||
<b>Description: </b>{payloadtypeData[selectedPayloadType]?.note}<br/>
|
||||
</Typography>
|
||||
</section>
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Select wrapper type
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Choose the wrapper family to configure for this build.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Select
|
||||
className="mythic-create-select"
|
||||
disabled={!props.first}
|
||||
value={selectedPayloadType}
|
||||
onChange={evt => setSelectedPayloadType(evt.target.value)}
|
||||
>
|
||||
{
|
||||
payloadtypesPerOS[os]?.map((opt) => (
|
||||
<MenuItem key={"step1" + opt} value={opt}>{opt}</MenuItem>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
<div className="mythic-create-agent-summary">
|
||||
<div className="mythic-create-agent-icon">
|
||||
<MythicAgentSVGIcon payload_type={selectedPayloadType} style={{width: "100%", height: "100%", objectFit: "contain"}} />
|
||||
</div>
|
||||
<div className="mythic-create-meta-list">
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Version</span>
|
||||
<div className="mythic-create-meta-value">{payloadtypeData[selectedPayloadType]?.semver || "Unknown"}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Description</span>
|
||||
<div className="mythic-create-meta-value">{payloadtypeData[selectedPayloadType]?.note || "No description available."}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Bottom section - scrollable table area */}
|
||||
<div style={{
|
||||
margin: "5px",
|
||||
border: props.first ? "1px solid grey" : '',
|
||||
borderRadius: "5px",
|
||||
padding: "10px 5px 5px 10px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{props.first &&
|
||||
<div style={{flexGrow: 1, overflowY: "auto", position: "relative"}}>
|
||||
<section className="mythic-create-section mythic-create-section-fill">
|
||||
{props.first ? (
|
||||
<div style={{display: "flex", flexDirection: "column", flexGrow: 1, minHeight: 0, overflow: "hidden", position: "relative"}}>
|
||||
{openBackdrop &&
|
||||
<Backdrop open={openBackdrop} onClick={()=>{setOpenBackdrop(false);}} style={{zIndex: 2000, position: "absolute"}}>
|
||||
<CircularProgress color="inherit" disableShrink />
|
||||
<MythicLoadingState compact title="Loading payloads" description="Fetching compatible payloads." sx={{color: "inherit"}} />
|
||||
</Backdrop>
|
||||
}
|
||||
<StartFromExistingPayloadOrStartFresh first={props.first}
|
||||
@@ -315,18 +308,15 @@ export function Step1SelectOS(props){
|
||||
onStartFresh={onStartFresh}
|
||||
/>
|
||||
</div>
|
||||
|
||||
}
|
||||
{!props.first &&
|
||||
) : (
|
||||
<ConfigureBuildParameters os={os} selectedPayloadType={selectedPayloadType}
|
||||
prevData={props.prevData}
|
||||
onUpdatePayloadConfig={onUpdatePayloadConfig} />
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Navigation buttons - always at bottom */}
|
||||
<div style={{flexShrink: 0}}>
|
||||
<div className="mythic-create-flow-footer">
|
||||
<CreatePayloadNavigationButtons
|
||||
first={props.first}
|
||||
last={props.last}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import {useQuery, gql} from '@apollo/client';
|
||||
import { CreatePayloadNavigationButtons} from './CreatePayloadNavigationButtons';
|
||||
import Typography from '@mui/material/Typography';
|
||||
@@ -7,7 +6,6 @@ import Button from '@mui/material/Button';
|
||||
import { snackActions } from '../../utilities/Snackbar';
|
||||
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 TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
@@ -20,41 +18,14 @@ import InfoIconOutline from '@mui/icons-material/InfoOutlined';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import {b64DecodeUnicode} from '../Callbacks/ResponseDisplay';
|
||||
import {MythicAgentSVGIcon} from "../../MythicComponents/MythicAgentSVGIcon";
|
||||
import { Backdrop, CircularProgress } from '@mui/material';
|
||||
import { Backdrop } from '@mui/material';
|
||||
import {PayloadsTableRowBuildStatus} from "../Payloads/PayloadsTableRowBuildStatus";
|
||||
import {PayloadsTableRowBuildProgress} from "../Payloads/PayloadsTableRowBuildProgress";
|
||||
import {MythicLoadingState, MythicTableEmptyState} from "../../MythicComponents/MythicStateDisplay";
|
||||
import MythicStyledTableCell from "../../MythicComponents/MythicTableCell";
|
||||
import {MythicClientSideTablePagination, useMythicClientPagination} from "../../MythicComponents/MythicTablePagination";
|
||||
|
||||
|
||||
const PREFIX = 'Step3SelectPayload';
|
||||
|
||||
const classes = {
|
||||
root: `${PREFIX}-root`,
|
||||
paper: `${PREFIX}-paper`,
|
||||
button: `${PREFIX}-button`
|
||||
};
|
||||
|
||||
const Root = styled('div')((
|
||||
{
|
||||
theme
|
||||
}
|
||||
) => ({
|
||||
[`& .${classes.root}`]: {
|
||||
margin: 'auto',
|
||||
width: "100%"
|
||||
},
|
||||
|
||||
[`& .${classes.paper}`]: {
|
||||
width: 200,
|
||||
height: 230,
|
||||
overflow: 'auto',
|
||||
|
||||
},
|
||||
|
||||
[`& .${classes.button}`]: {
|
||||
margin: theme.spacing(0.5, 0),
|
||||
}
|
||||
}));
|
||||
|
||||
const GET_Payload_Types = gql`
|
||||
query getWrappablePayloads($payloadType: Int!) {
|
||||
payloadtype_by_pk(id: $payloadType) {
|
||||
@@ -107,6 +78,7 @@ export function Step3SelectPayload(props){
|
||||
setOpenBackdrop(false);
|
||||
}else{
|
||||
snackActions.warning("No supported payload for that wrapper");
|
||||
setOpenBackdrop(false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -122,71 +94,66 @@ export function Step3SelectPayload(props){
|
||||
props.canceled();
|
||||
}
|
||||
return (
|
||||
<div style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}>
|
||||
{/* Content area that can grow */}
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
minHeight: 0 // Important for flex shrinking
|
||||
}}>
|
||||
{/* Top section - fixed height */}
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexShrink: 0 // Don't shrink this section
|
||||
}}>
|
||||
<div style={{display: "flex", width: "100%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px"}}>
|
||||
<MythicAgentSVGIcon payload_type={props.buildOptions.payload_type} style={{width: "80px", padding: "5px", objectFit: "unset"}} />
|
||||
<div className="mythic-create-flow-shell">
|
||||
<div className="mythic-create-flow-content">
|
||||
<div className="mythic-create-selection-grid">
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-agent-summary">
|
||||
<div className="mythic-create-agent-icon">
|
||||
<MythicAgentSVGIcon payload_type={props.buildOptions.payload_type} style={{width: "100%", height: "100%", objectFit: "contain"}} />
|
||||
</div>
|
||||
<div className="mythic-create-meta-list">
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Operating system</span>
|
||||
<div className="mythic-create-meta-value">{props.buildOptions.os}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Description</span>
|
||||
<div className="mythic-create-meta-value">{props.buildOptions.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Select payload to include
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Pick the existing payload that this wrapper should embed.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section className="mythic-create-section mythic-create-section-fill">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography variant={"p"} style={{}}>
|
||||
<b>OS: </b>{props.buildOptions.os}
|
||||
</Typography><br/>
|
||||
<Typography variant="body2" component="p" style={{whiteSpace: "pre-wrap"}}>
|
||||
<b>Description: </b>{props.buildOptions.description}
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Compatible payloads
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
Only payloads matching the selected wrapper type and operating system are shown.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{width: "100%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px"}}>
|
||||
<div style={{width: "100%", display: "flex", alignItems: "flex-start", marginBottom: "10px", flexDirection: "column"}}>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom section - scrollable table area */}
|
||||
<div style={{
|
||||
margin: "5px",
|
||||
border: props.first ? "1px solid grey" : '',
|
||||
borderRadius: "5px",
|
||||
padding: "10px 5px 5px 10px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
1. Select Payload to Include
|
||||
</Typography>
|
||||
<div style={{flexGrow: 1, overflowY: "auto", position: "relative"}}>
|
||||
<div style={{display: "flex", flexDirection: "column", flexGrow: 1, minHeight: 0, overflow: "hidden", position: "relative"}}>
|
||||
{openBackdrop &&
|
||||
<Backdrop open={openBackdrop} onClick={()=>{setOpenBackdrop(false);}} style={{zIndex: 2000, position: "absolute"}}>
|
||||
<CircularProgress color="inherit" disableShrink />
|
||||
<Backdrop open={openBackdrop} style={{zIndex: 2000, position: "absolute"}}>
|
||||
<MythicLoadingState compact title="Loading payloads" description="Fetching compatible payloads." sx={{color: "inherit"}} />
|
||||
</Backdrop>
|
||||
}
|
||||
<PayloadSelect payloadOptions={payloadOptions} first={props.first} last={props.last}
|
||||
canceled={canceled} finished={finished}/>
|
||||
{!openBackdrop &&
|
||||
<PayloadSelect payloadOptions={payloadOptions} first={props.first} last={props.last}
|
||||
canceled={canceled} finished={finished}/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Navigation buttons - always at bottom */}
|
||||
<div style={{flexShrink: 0}}>
|
||||
<div className="mythic-create-flow-footer">
|
||||
<CreatePayloadNavigationButtons disableNext first={props.first} last={props.last}
|
||||
canceled={props.canceled} finished={finished}/>
|
||||
<br/><br/>
|
||||
@@ -196,34 +163,61 @@ export function Step3SelectPayload(props){
|
||||
}
|
||||
|
||||
export function PayloadSelect(props) {
|
||||
const payloadOptions = React.useMemo(() => {
|
||||
return Array.isArray(props.payloadOptions) ? props.payloadOptions : [];
|
||||
}, [props.payloadOptions]);
|
||||
const resetKey = React.useMemo(() => {
|
||||
return payloadOptions.map((payload) => payload.id).join(",");
|
||||
}, [payloadOptions]);
|
||||
const pagination = useMythicClientPagination({
|
||||
items: payloadOptions,
|
||||
resetKey,
|
||||
rowsPerPage: 25,
|
||||
});
|
||||
const finished = (payload) => {
|
||||
props.finished(payload);
|
||||
}
|
||||
return (
|
||||
<TableContainer className="mythicElement" style={{height: "100%", overflow: "auto"}}>
|
||||
<Table stickyHeader size="small" style={{tableLayout:"fixed", maxWidth: "100%",}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "6rem"}}> Select</TableCell>
|
||||
<TableCell style={{width: "15rem"}}>Timestamp</TableCell>
|
||||
<TableCell>File</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell style={{width: "5rem"}}>Details</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<div style={{display: "flex", flexDirection: "column", height: "100%", minHeight: 0}}>
|
||||
<TableContainer
|
||||
className="mythicElement mythic-fixed-row-table-wrap"
|
||||
style={{flex: "1 1 auto", minHeight: 0, overflowY: "auto"}}
|
||||
>
|
||||
<Table stickyHeader size="small" style={{tableLayout:"fixed", height: "auto", maxWidth: "100%"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell style={{width: "6rem"}}>Select</MythicStyledTableCell>
|
||||
<MythicStyledTableCell style={{width: "15rem"}}>Timestamp</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>File</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Status</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Description</MythicStyledTableCell>
|
||||
<MythicStyledTableCell style={{width: "5rem"}}>Details</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
||||
{props.payloadOptions?.map( (op) => (
|
||||
<PayloadsTableRow
|
||||
onSelected={finished}
|
||||
key={"payload" + op.id}
|
||||
payload={op}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{payloadOptions.length === 0 ? (
|
||||
<MythicTableEmptyState
|
||||
colSpan={6}
|
||||
compact
|
||||
title="No compatible payloads"
|
||||
description="Start fresh or adjust the selected payload type and operating system."
|
||||
minHeight={180}
|
||||
/>
|
||||
) : (
|
||||
pagination.pageData.map( (op) => (
|
||||
<PayloadsTableRow
|
||||
onSelected={finished}
|
||||
key={"payload" + op.id}
|
||||
payload={op}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<MythicClientSideTablePagination id="create-payload-select-pagination" pagination={pagination} />
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
@@ -236,33 +230,35 @@ export function PayloadsTableRow(props){
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TableRow key={"payload" + props.payload.uuid} hover>
|
||||
<TableCell>
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-success" size="small" onClick={onSelected} variant="contained">Select</Button>
|
||||
</TableCell>
|
||||
<TableCell>{toLocalTime(props.payload.creation_time, me.user.view_utc_time)}</TableCell>
|
||||
<TableCell>{b64DecodeUnicode(props.payload.filemetum.filename_text)}</TableCell>
|
||||
<TableCell>
|
||||
<TableRow key={"payload" + props.payload.uuid} hover onClick={onSelected} style={{cursor: "pointer"}}>
|
||||
<MythicStyledTableCell>
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-info" size="small" onClick={(event) => {event.stopPropagation(); onSelected();}} variant="contained">Select</Button>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>{toLocalTime(props.payload.creation_time, me.user.view_utc_time)}</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>{b64DecodeUnicode(props.payload.filemetum.filename_text)}</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<div className="mythic-payload-progress-cell">
|
||||
<PayloadsTableRowBuildStatus {...props.payload} />
|
||||
<PayloadsTableRowBuildProgress {...props.payload} />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{props.payload.description}</TableCell>
|
||||
<TableCell>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => setOpenDetailedView(true)}>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>{props.payload.description}</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={(event) => {event.stopPropagation(); setOpenDetailedView(true);}}>
|
||||
<InfoIconOutline fontSize="small" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
{openDetailedView ? (
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openDetailedView} me={me}
|
||||
onClose={()=>{setOpenDetailedView(false);}}
|
||||
innerDialog={<DetailedPayloadTable {...props.payload} me={me} payload_id={props.payload.id} onClose={()=>{setOpenDetailedView(false);}} />}
|
||||
/>
|
||||
<TableRow style={{display: "none"}}>
|
||||
<MythicStyledTableCell colSpan={6}>
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openDetailedView} me={me}
|
||||
onClose={()=>{setOpenDetailedView(false);}}
|
||||
innerDialog={<DetailedPayloadTable {...props.payload} me={me} payload_id={props.payload.id} onClose={()=>{setOpenDetailedView(false);}} />}
|
||||
/>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
) : null }
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -158,78 +158,55 @@ export function Step5Build(props){
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}>
|
||||
{/* Content area that can grow */}
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
minHeight: 0 // Important for flex shrinking
|
||||
}}>
|
||||
{/* Top section - fixed height */}
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexShrink: 0 // Don't shrink this section
|
||||
}}>
|
||||
<div style={{width: "100%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px", display: "flex"}}>
|
||||
<MythicAgentSVGIcon payload_type={props.buildOptions[1].payload_type} style={{width: "80px", padding: "5px", objectFit: "unset"}} />
|
||||
<div>
|
||||
<Typography variant={"p"} style={{}}>
|
||||
<b>OS: </b>{props.buildOptions[1].os}
|
||||
<div className="mythic-create-flow-shell">
|
||||
<div className="mythic-create-flow-content">
|
||||
<div className="mythic-create-selection-grid">
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-agent-summary">
|
||||
<div className="mythic-create-agent-icon">
|
||||
<MythicAgentSVGIcon payload_type={props.buildOptions[1].payload_type} style={{width: "100%", height: "100%", objectFit: "contain"}} />
|
||||
</div>
|
||||
<div className="mythic-create-meta-list">
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Operating system</span>
|
||||
<div className="mythic-create-meta-value">{props.buildOptions[1].os}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mythic-create-meta-label">Description</span>
|
||||
<div className="mythic-create-meta-value">{props.buildOptions[1].description}</div>
|
||||
</div>
|
||||
<MythicStyledTooltip title={"Edit OS / Payload Type"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => props.moveToStep(0)}>
|
||||
<DriveFileRenameOutlineIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</Typography><br/>
|
||||
<Typography variant="body2" component="p" style={{whiteSpace: "pre-wrap"}}>
|
||||
<b>Description: </b>{props.buildOptions[1].description}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{width: "100%", margin: "5px", border: "1px solid grey", borderRadius: "5px", padding: "10px"}}>
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
1. Provide Payload Name and Description
|
||||
</Typography>
|
||||
<div style={{width: "100%", display: "flex", alignItems: "flex-start", marginBottom: "10px", flexDirection: "column"}}>
|
||||
</section>
|
||||
<section className="mythic-create-section">
|
||||
<div className="mythic-create-section-header">
|
||||
<div>
|
||||
<Typography component="div" className="mythic-create-section-title">
|
||||
Wrapper name and description
|
||||
</Typography>
|
||||
<Typography component="div" className="mythic-create-section-description">
|
||||
These values are used for the wrapper file and operator-facing description.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mythic-form">
|
||||
<MythicTextField onEnter={finished} autoFocus={true} required={false} placeholder={"Filename"}
|
||||
value={filename} multiline={false} onChange={onChangeFilename} display="inline-block"/>
|
||||
<MythicTextField onEnter={finished} required={false} placeholder={"description"} value={description}
|
||||
multiline={false} onChange={onChangeDescription} display="inline-block"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Bottom section - scrollable table area */}
|
||||
<div style={{
|
||||
margin: "5px",
|
||||
// border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
//padding: "10px 5px 5px 10px",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
<div style={{
|
||||
width: "30%",
|
||||
margin: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
padding: "5px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "auto"
|
||||
}}>
|
||||
<Typography textAlign="center" variant={"h7"} style={{fontWeight: 600, width: "100%"}}>
|
||||
2. Build Parameter Configuration
|
||||
<div className="mythic-create-builder-split">
|
||||
<section className="mythic-create-section mythic-create-section-scroll">
|
||||
<Typography component="div" className="mythic-create-section-title" style={{textAlign: "center"}}>
|
||||
Build parameter configuration
|
||||
<MythicStyledTooltip title={"Edit Build Parameters"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => props.moveToStep(1)}>
|
||||
<DriveFileRenameOutlineIcon />
|
||||
@@ -237,21 +214,10 @@ export function Step5Build(props){
|
||||
</MythicStyledTooltip>
|
||||
</Typography>
|
||||
<ConfigurationSummary buildParameters={props.buildOptions[1].parameters} os={props.buildOptions[1].os} />
|
||||
</div>
|
||||
<div style={{
|
||||
width: "70%",
|
||||
margin: "5px",
|
||||
border: "1px solid grey",
|
||||
borderRadius: "5px",
|
||||
padding: "5px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
minHeight: 0, // Important for flex shrinking
|
||||
overflow: "auto"
|
||||
}}>
|
||||
<Typography textAlign="center" variant={"h7"} style={{fontWeight: 600, width: "100%"}}>
|
||||
3. Embedded Payload Configuration
|
||||
</section>
|
||||
<section className="mythic-create-section mythic-create-section-scroll">
|
||||
<Typography component="div" className="mythic-create-section-title" style={{textAlign: "center"}}>
|
||||
Embedded payload configuration
|
||||
<MythicStyledTooltip title={"Edit Selected Payload"}>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-info" size="small" onClick={() => props.moveToStep(2)}>
|
||||
<DriveFileRenameOutlineIcon />
|
||||
@@ -274,13 +240,11 @@ export function Step5Build(props){
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Navigation buttons - always at bottom */}
|
||||
<div style={{flexShrink: 0}}>
|
||||
<div className="mythic-create-flow-footer">
|
||||
<CreatePayloadNavigationButtons
|
||||
first={props.first}
|
||||
last={props.last}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@ import CheckCircleTwoToneIcon from '@mui/icons-material/CheckCircleTwoTone';
|
||||
import CancelTwoToneIcon from '@mui/icons-material/CancelTwoTone';
|
||||
import {snackActions} from "../../utilities/Snackbar";
|
||||
import {MythicStatusChip} from "../../MythicComponents/MythicStatusChip";
|
||||
import {MythicTableEmptyState} from "../../MythicComponents/MythicStateDisplay";
|
||||
|
||||
const updateApprovalStatusMutation = gql`
|
||||
mutation updateApprovalStatus($eventgroupapproval_id: Int!, $approved: Boolean!) {
|
||||
@@ -76,54 +77,63 @@ export function EventGroupTableRunAsDialog({eventgroupapprovals, me, onClose, se
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{eventgroupapprovals.map( e => (
|
||||
<TableRow key={e.id}>
|
||||
<TableCell>{e.operator.username}</TableCell>
|
||||
<TableCell>
|
||||
{e.approved ? (
|
||||
<div className="mythic-table-row-actions">
|
||||
<MythicStatusChip label="Approved" status="success" icon={<CheckCircleTwoToneIcon />} />
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-warning" disabled={e.operator.id !== me?.user?.id} variant={"contained"}
|
||||
startIcon={<CancelTwoToneIcon fontSize="small" />}
|
||||
onClick={() => onApprovalClick({id: e.id, approved: false})}>
|
||||
Deny
|
||||
</Button>
|
||||
</div>
|
||||
{eventgroupapprovals.length === 0 ? (
|
||||
<MythicTableEmptyState
|
||||
colSpan={3}
|
||||
compact
|
||||
title="No approvals needed"
|
||||
description="This workflow does not have pending operator approvals."
|
||||
/>
|
||||
) : (
|
||||
eventgroupapprovals.map( e => (
|
||||
<TableRow key={e.id}>
|
||||
<TableCell>{e.operator.username}</TableCell>
|
||||
<TableCell>
|
||||
{e.approved ? (
|
||||
<div className="mythic-table-row-actions">
|
||||
<MythicStatusChip label="Approved" status="success" icon={<CheckCircleTwoToneIcon />} />
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-warning" disabled={e.operator.id !== me?.user?.id} variant={"contained"}
|
||||
startIcon={<CancelTwoToneIcon fontSize="small" />}
|
||||
onClick={() => onApprovalClick({id: e.id, approved: false})}>
|
||||
Deny
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
) : e.created_at === e.updated_at ? (
|
||||
<div className="mythic-table-row-actions">
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-success" disabled={e.operator.id !== me?.user?.id}
|
||||
variant={"contained"}
|
||||
startIcon={<CheckCircleTwoToneIcon fontSize="small" />}
|
||||
onClick={() => onApprovalClick({id: e.id, approved: true})}>
|
||||
Approve
|
||||
</Button>
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-warning" disabled={e.operator.id !== me?.user?.id} variant={"contained"}
|
||||
startIcon={<CancelTwoToneIcon fontSize="small" />}
|
||||
onClick={() => onApprovalClick({id: e.id, approved: false})}>
|
||||
Deny
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mythic-table-row-actions">
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-success" variant={"contained"}
|
||||
disabled={e.operator.id !== me?.user?.id}
|
||||
startIcon={<CheckCircleTwoToneIcon fontSize="small" />}
|
||||
onClick={() => onApprovalClick({id: e.id, approved: true})}>
|
||||
Approve
|
||||
</Button>
|
||||
<MythicStatusChip label="Denied" status="warning" icon={<CancelTwoToneIcon />} />
|
||||
) : e.created_at === e.updated_at ? (
|
||||
<div className="mythic-table-row-actions">
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-success" disabled={e.operator.id !== me?.user?.id}
|
||||
variant={"contained"}
|
||||
startIcon={<CheckCircleTwoToneIcon fontSize="small" />}
|
||||
onClick={() => onApprovalClick({id: e.id, approved: true})}>
|
||||
Approve
|
||||
</Button>
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-warning" disabled={e.operator.id !== me?.user?.id} variant={"contained"}
|
||||
startIcon={<CancelTwoToneIcon fontSize="small" />}
|
||||
onClick={() => onApprovalClick({id: e.id, approved: false})}>
|
||||
Deny
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mythic-table-row-actions">
|
||||
<Button className="mythic-table-row-action mythic-table-row-action-hover-success" variant={"contained"}
|
||||
disabled={e.operator.id !== me?.user?.id}
|
||||
startIcon={<CheckCircleTwoToneIcon fontSize="small" />}
|
||||
onClick={() => onApprovalClick({id: e.id, approved: true})}>
|
||||
Approve
|
||||
</Button>
|
||||
<MythicStatusChip label="Denied" status="warning" icon={<CancelTwoToneIcon />} />
|
||||
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{e.created_at !== e.updated_at ? (
|
||||
toLocalTime(e?.updated_at, me?.user?.view_utc_time)
|
||||
) : null}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{e.created_at !== e.updated_at ? (
|
||||
toLocalTime(e?.updated_at, me?.user?.view_utc_time)
|
||||
) : null}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
@@ -24,6 +24,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import {IconButton, Link, Typography} from '@mui/material';
|
||||
import {MythicConfirmDialog} from "../../MythicComponents/MythicConfirmDialog";
|
||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||
import {MythicTableEmptyState} from "../../MythicComponents/MythicStateDisplay";
|
||||
|
||||
export function EventFileManageDialog({onClose, selectedEventGroup}) {
|
||||
|
||||
@@ -60,9 +61,18 @@ export function EventFileManageDialog({onClose, selectedEventGroup}) {
|
||||
<TableContainer className="mythicElement">
|
||||
<Table>
|
||||
<TableBody>
|
||||
{selectedEventGroup.filemeta.map( e => (
|
||||
<EventFileManageDialogTableRow key={e.id} eventFile={e} />
|
||||
))}
|
||||
{selectedEventGroup.filemeta.length === 0 ? (
|
||||
<MythicTableEmptyState
|
||||
colSpan={1}
|
||||
compact
|
||||
title="No workflow files"
|
||||
description="Uploaded files for this workflow will appear here."
|
||||
/>
|
||||
) : (
|
||||
selectedEventGroup.filemeta.map( e => (
|
||||
<EventFileManageDialogTableRow key={e.id} eventFile={e} />
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
@@ -13,6 +13,7 @@ import TableRow from '@mui/material/TableRow';
|
||||
import MythicStyledTableCell from "../../MythicComponents/MythicTableCell";
|
||||
import {Typography} from '@mui/material';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import {MythicTableEmptyState} from "../../MythicComponents/MythicStateDisplay";
|
||||
|
||||
export function EventGroupConsumingContainersDialog({onClose, selectedEventGroup}) {
|
||||
|
||||
@@ -34,9 +35,18 @@ export function EventGroupConsumingContainersDialog({onClose, selectedEventGroup
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{selectedEventGroup.eventgroupconsumingcontainers.map( e => (
|
||||
<EventGroupConsumingContainersDialogTableRow key={e.id} container={e} />
|
||||
))}
|
||||
{selectedEventGroup.eventgroupconsumingcontainers.length === 0 ? (
|
||||
<MythicTableEmptyState
|
||||
colSpan={4}
|
||||
compact
|
||||
title="No container requirements"
|
||||
description="This workflow does not declare required eventing containers."
|
||||
/>
|
||||
) : (
|
||||
selectedEventGroup.eventgroupconsumingcontainers.map( e => (
|
||||
<EventGroupConsumingContainersDialogTableRow key={e.id} container={e} />
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
@@ -58,6 +58,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import {getStringSize} from "../Callbacks/ResponseDisplayTable";
|
||||
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
import {MythicEmptyState, MythicErrorState, MythicLoadingState} from "../../MythicComponents/MythicStateDisplay";
|
||||
import {MythicClientSideTablePagination, useMythicClientPagination} from "../../MythicComponents/MythicTablePagination";
|
||||
|
||||
|
||||
const getEventSteps = gql`
|
||||
@@ -1492,7 +1493,12 @@ function EventStepDetailDialog({selectedEventStep, onClose}) {
|
||||
)
|
||||
}
|
||||
function EventDetailsCallbacksTable({callbacks, includeContext = false}){
|
||||
const callbackCount = callbacks?.length || 0;
|
||||
const callbackRows = callbacks || [];
|
||||
const callbackCount = callbackRows.length;
|
||||
const pagination = useMythicClientPagination({
|
||||
items: callbackRows,
|
||||
resetKey: includeContext ? "with-context" : "without-context",
|
||||
});
|
||||
return (
|
||||
<EventingDetailSection collapsible title="Callbacks generated" count={callbackCount}>
|
||||
{callbackCount === 0 ? (
|
||||
@@ -1501,43 +1507,50 @@ function EventDetailsCallbacksTable({callbacks, includeContext = false}){
|
||||
description="This workflow has not produced any callbacks yet."
|
||||
/>
|
||||
) : (
|
||||
<TableContainer className="mythicElement mythic-eventing-detail-table-wrap">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell>Callback</MythicStyledTableCell>
|
||||
{includeContext &&
|
||||
<MythicStyledTableCell>Context</MythicStyledTableCell>
|
||||
}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{callbacks.map(trackedData => (
|
||||
<TableRow key={"callbacks" + trackedData.id} hover>
|
||||
<MythicStyledTableCell>
|
||||
<Link className="mythic-eventing-resource-link" color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/callbacks/" + trackedData.display_id}>{trackedData.display_id}</Link>
|
||||
</MythicStyledTableCell>
|
||||
<>
|
||||
<TableContainer className="mythicElement mythic-eventing-detail-table-wrap mythic-fixed-row-table-wrap">
|
||||
<Table style={{height: "auto"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell>Callback</MythicStyledTableCell>
|
||||
{includeContext &&
|
||||
<MythicStyledTableCell>
|
||||
<span className="mythic-eventing-resource-secondary">
|
||||
{trackedData.user || "unknown"} @ {trackedData.host || "unknown"}
|
||||
{trackedData.pid ? ` (${trackedData.pid})` : ""}
|
||||
{trackedData.process_name ? ` - ${trackedData.process_name}` : ""}
|
||||
</span>
|
||||
Context
|
||||
</MythicStyledTableCell>
|
||||
}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{pagination.pageData.map(trackedData => (
|
||||
<TableRow key={"callbacks" + trackedData.id} hover>
|
||||
<MythicStyledTableCell>
|
||||
<Link className="mythic-eventing-resource-link" color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/callbacks/" + trackedData.display_id}>{trackedData.display_id}</Link>
|
||||
</MythicStyledTableCell>
|
||||
{includeContext &&
|
||||
<MythicStyledTableCell>
|
||||
<span className="mythic-eventing-resource-secondary">
|
||||
{trackedData.user || "unknown"} @ {trackedData.host || "unknown"}
|
||||
{trackedData.pid ? ` (${trackedData.pid})` : ""}
|
||||
{trackedData.process_name ? ` - ${trackedData.process_name}` : ""}
|
||||
</span>
|
||||
</MythicStyledTableCell>
|
||||
}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<MythicClientSideTablePagination pagination={pagination} />
|
||||
</>
|
||||
)}
|
||||
</EventingDetailSection>
|
||||
)
|
||||
}
|
||||
function EventDetailsAPITokensTable({tokens}){
|
||||
const tokenCount = tokens?.length || 0;
|
||||
const tokenRows = tokens || [];
|
||||
const tokenCount = tokenRows.length;
|
||||
const pagination = useMythicClientPagination({items: tokenRows});
|
||||
return (
|
||||
<EventingDetailSection collapsible title="API tokens generated" count={tokenCount}>
|
||||
{tokenCount === 0 ? (
|
||||
@@ -1546,36 +1559,41 @@ function EventDetailsAPITokensTable({tokens}){
|
||||
description="This workflow has not created any API tokens."
|
||||
/>
|
||||
) : (
|
||||
<TableContainer className="mythicElement mythic-eventing-detail-table-wrap">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "2rem"}}></TableCell>
|
||||
<TableCell style={{width: "5rem"}}>Active</TableCell>
|
||||
<TableCell style={{width: "12rem"}}>Created By</TableCell>
|
||||
<TableCell style={{width: "7rem"}}>Token</TableCell>
|
||||
<TableCell style={{width: "9rem"}}>Type</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{tokens.map(trackedData => (
|
||||
<APITokenRow key={"apitoken" + trackedData.id} {...trackedData}
|
||||
onToggleActive={() => {}}
|
||||
onDeleteAPIToken={() =>{}}
|
||||
>
|
||||
</APITokenRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<>
|
||||
<TableContainer className="mythicElement mythic-eventing-detail-table-wrap mythic-fixed-row-table-wrap">
|
||||
<Table style={{height: "auto"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "2rem"}}></TableCell>
|
||||
<TableCell style={{width: "5rem"}}>Active</TableCell>
|
||||
<TableCell style={{width: "12rem"}}>Created By</TableCell>
|
||||
<TableCell style={{width: "7rem"}}>Token</TableCell>
|
||||
<TableCell style={{width: "9rem"}}>Type</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{pagination.pageData.map(trackedData => (
|
||||
<APITokenRow key={"apitoken" + trackedData.id} {...trackedData}
|
||||
onToggleActive={() => {}}
|
||||
onDeleteAPIToken={() =>{}}
|
||||
>
|
||||
</APITokenRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<MythicClientSideTablePagination pagination={pagination} />
|
||||
</>
|
||||
)}
|
||||
</EventingDetailSection>
|
||||
)
|
||||
}
|
||||
function EventDetailsPayloadsTable({payloads, deletePayload}){
|
||||
const payloadCount = payloads?.length || 0;
|
||||
const payloadRows = payloads || [];
|
||||
const payloadCount = payloadRows.length;
|
||||
const pagination = useMythicClientPagination({items: payloadRows});
|
||||
const [openDetailedView, setOpenDetailedView] = React.useState(false);
|
||||
const [openDelete, setOpenDeleteDialog] = React.useState(false);
|
||||
const selectedPayloadRef = React.useRef({});
|
||||
@@ -1598,9 +1616,9 @@ function EventDetailsPayloadsTable({payloads, deletePayload}){
|
||||
const onDownloadBulkPayloads = () => {
|
||||
snackActions.info("Zipping up files...");
|
||||
let fileIds = [];
|
||||
for(let i = 0; i < payloads.length; i++){
|
||||
if(payloads[i].build_phase === "success" && !payloads[i].deleted){
|
||||
fileIds.push(payloads[i].filemetum.agent_file_id);
|
||||
for(let i = 0; i < payloadRows.length; i++){
|
||||
if(payloadRows[i].build_phase === "success" && !payloadRows[i].deleted){
|
||||
fileIds.push(payloadRows[i].filemetum.agent_file_id);
|
||||
}
|
||||
}
|
||||
downloadBulk({variables:{files: fileIds}})
|
||||
@@ -1647,8 +1665,9 @@ function EventDetailsPayloadsTable({payloads, deletePayload}){
|
||||
description="This workflow has not generated any payloads."
|
||||
/>
|
||||
) : (
|
||||
<TableContainer className="mythicElement mythic-eventing-detail-table-wrap">
|
||||
<Table>
|
||||
<>
|
||||
<TableContainer className="mythicElement mythic-eventing-detail-table-wrap mythic-fixed-row-table-wrap">
|
||||
<Table style={{height: "auto"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell style={{width: "2rem"}}></MythicStyledTableCell>
|
||||
@@ -1659,7 +1678,7 @@ function EventDetailsPayloadsTable({payloads, deletePayload}){
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{payloads.map(trackedData => (
|
||||
{pagination.pageData.map(trackedData => (
|
||||
<TableRow key={"payloads" + trackedData.id} hover>
|
||||
<MythicStyledTableCell style={{width: "2rem"}}>
|
||||
{!trackedData.deleted &&
|
||||
@@ -1689,13 +1708,17 @@ function EventDetailsPayloadsTable({payloads, deletePayload}){
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<MythicClientSideTablePagination pagination={pagination} />
|
||||
</>
|
||||
)}
|
||||
</EventingDetailSection>
|
||||
</>
|
||||
)
|
||||
}
|
||||
function EventDetailsTaskTable({tasks}){
|
||||
const taskCount = tasks?.length || 0;
|
||||
const taskRows = tasks || [];
|
||||
const taskCount = taskRows.length;
|
||||
const pagination = useMythicClientPagination({items: taskRows});
|
||||
return (
|
||||
<EventingDetailSection collapsible title="Tasks issued" count={taskCount}>
|
||||
{taskCount === 0 ? (
|
||||
@@ -1704,51 +1727,56 @@ function EventDetailsTaskTable({tasks}){
|
||||
description="This workflow has not issued any tasks."
|
||||
/>
|
||||
) : (
|
||||
<TableContainer className="mythicElement mythic-eventing-detail-table-wrap">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell style={{width: "4rem"}}>Type</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Callback</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Task</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Command</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Operator</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{tasks.map(trackedData => (
|
||||
<TableRow key={"tasks" + trackedData.id} hover>
|
||||
<MythicStyledTableCell style={{width: "4rem"}}>
|
||||
{trackedData.callback?.payload?.payloadtype?.name &&
|
||||
<MythicAgentSVGIcon payload_type={trackedData.callback.payload.payloadtype.name}
|
||||
style={{height: "40px"}} />
|
||||
}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Link className="mythic-eventing-resource-link" color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/callbacks/" + trackedData.callback.display_id}>{trackedData.callback.display_id}</Link>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Link className="mythic-eventing-resource-link" color={"textPrimary"} underline={"always"} target={"_blank"}
|
||||
href={"/new/task/" + trackedData.display_id}>{trackedData.display_id}</Link>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<span className="mythic-eventing-resource-command">{trackedData.command_name}</span> {trackedData.display_params}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{trackedData.operator.username}
|
||||
</MythicStyledTableCell>
|
||||
<>
|
||||
<TableContainer className="mythicElement mythic-eventing-detail-table-wrap mythic-fixed-row-table-wrap">
|
||||
<Table style={{height: "auto"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell style={{width: "4rem"}}>Type</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Callback</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Task</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Command</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Operator</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{pagination.pageData.map(trackedData => (
|
||||
<TableRow key={"tasks" + trackedData.id} hover>
|
||||
<MythicStyledTableCell style={{width: "4rem"}}>
|
||||
{trackedData.callback?.payload?.payloadtype?.name &&
|
||||
<MythicAgentSVGIcon payload_type={trackedData.callback.payload.payloadtype.name}
|
||||
style={{height: "40px"}} />
|
||||
}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Link className="mythic-eventing-resource-link" color="textPrimary" underline="always" target="_blank"
|
||||
href={"/new/callbacks/" + trackedData.callback.display_id}>{trackedData.callback.display_id}</Link>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Link className="mythic-eventing-resource-link" color={"textPrimary"} underline={"always"} target={"_blank"}
|
||||
href={"/new/task/" + trackedData.display_id}>{trackedData.display_id}</Link>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<span className="mythic-eventing-resource-command">{trackedData.command_name}</span> {trackedData.display_params}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{trackedData.operator.username}
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<MythicClientSideTablePagination pagination={pagination} />
|
||||
</>
|
||||
)}
|
||||
</EventingDetailSection>
|
||||
)
|
||||
}
|
||||
function EventDetailsFilesTable({files}){
|
||||
const fileCount = files?.length || 0;
|
||||
const fileRows = files || [];
|
||||
const fileCount = fileRows.length;
|
||||
const pagination = useMythicClientPagination({items: fileRows});
|
||||
const [downloadBulk] = useMutation(downloadBulkQuery, {
|
||||
onCompleted: (data) => {
|
||||
snackActions.dismiss();
|
||||
@@ -1768,9 +1796,9 @@ function EventDetailsFilesTable({files}){
|
||||
const onDownloadBulkPayloads = () => {
|
||||
snackActions.info("Zipping up files...");
|
||||
let fileIds = [];
|
||||
for(let i = 0; i < files.length; i++){
|
||||
if( !files[i].deleted){
|
||||
fileIds.push(files[i].agent_file_id);
|
||||
for(let i = 0; i < fileRows.length; i++){
|
||||
if( !fileRows[i].deleted){
|
||||
fileIds.push(fileRows[i].agent_file_id);
|
||||
}
|
||||
}
|
||||
downloadBulk({variables:{files: fileIds}})
|
||||
@@ -1795,45 +1823,48 @@ function EventDetailsFilesTable({files}){
|
||||
description="This workflow has not tracked any files."
|
||||
/>
|
||||
) : (
|
||||
<TableContainer className="mythicElement mythic-eventing-detail-table-wrap">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell>File name</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Comment</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Size</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{files.map(trackedData => (
|
||||
<TableRow key={"filemeta" + trackedData.id} hover>
|
||||
<MythicStyledTableCell>
|
||||
{trackedData.deleted ? (
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>
|
||||
{b64DecodeUnicode(trackedData.full_remote_path_text) === "" ?
|
||||
b64DecodeUnicode(trackedData.filename_text) :
|
||||
b64DecodeUnicode(trackedData.full_remote_path_text)}
|
||||
</Typography>
|
||||
) : (
|
||||
<Link className="mythic-eventing-resource-link" color="textPrimary" underline="always" href={"/direct/download/" + trackedData.agent_file_id}>
|
||||
{b64DecodeUnicode(trackedData.full_remote_path_text) === "" ?
|
||||
b64DecodeUnicode(trackedData.filename_text) :
|
||||
b64DecodeUnicode(trackedData.full_remote_path_text)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{trackedData.comment}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{getStringSize({cellData: {"plaintext": trackedData.size}})}
|
||||
</MythicStyledTableCell>
|
||||
<>
|
||||
<TableContainer className="mythicElement mythic-eventing-detail-table-wrap mythic-fixed-row-table-wrap">
|
||||
<Table style={{height: "auto"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell>File name</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Comment</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Size</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{pagination.pageData.map(trackedData => (
|
||||
<TableRow key={"filemeta" + trackedData.id} hover>
|
||||
<MythicStyledTableCell>
|
||||
{trackedData.deleted ? (
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}>
|
||||
{b64DecodeUnicode(trackedData.full_remote_path_text) === "" ?
|
||||
b64DecodeUnicode(trackedData.filename_text) :
|
||||
b64DecodeUnicode(trackedData.full_remote_path_text)}
|
||||
</Typography>
|
||||
) : (
|
||||
<Link className="mythic-eventing-resource-link" color="textPrimary" underline="always" href={"/direct/download/" + trackedData.agent_file_id}>
|
||||
{b64DecodeUnicode(trackedData.full_remote_path_text) === "" ?
|
||||
b64DecodeUnicode(trackedData.filename_text) :
|
||||
b64DecodeUnicode(trackedData.full_remote_path_text)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{trackedData.comment}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{getStringSize({cellData: {"plaintext": trackedData.size}})}
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<MythicClientSideTablePagination pagination={pagination} />
|
||||
</>
|
||||
)}
|
||||
</EventingDetailSection>
|
||||
)
|
||||
|
||||
@@ -252,7 +252,9 @@ function CommandBlockListTableRow(props){
|
||||
<TableRow hover>
|
||||
<TableCell>
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-danger" size="small" onClick={()=>{setOpenDeleteDialog(true);}}><DeleteIcon fontSize="small" /></IconButton>
|
||||
<MythicConfirmDialog onClose={() => {setOpenDeleteDialog(false);}} onSubmit={onAcceptDelete} open={openDelete}/>
|
||||
{openDelete &&
|
||||
<MythicConfirmDialog onClose={() => {setOpenDeleteDialog(false);}} onSubmit={onAcceptDelete} open={openDelete}/>
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button className="mythic-table-row-action" size="small" onClick={()=>{setOpenUpdateDialog(true);}} startIcon={<EditIcon/>} variant="outlined">Edit</Button>
|
||||
|
||||
@@ -4,6 +4,7 @@ import TableRow from '@mui/material/TableRow';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import MythicStyledTableCell from "../../MythicComponents/MythicTableCell";
|
||||
import {toLocalTime} from "../../utilities/Time";
|
||||
|
||||
export function APITokenRow(props){
|
||||
return (
|
||||
@@ -26,7 +27,16 @@ export function APITokenRow(props){
|
||||
name="active"
|
||||
/>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>{props.created_by_operator?.username}</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<Typography>
|
||||
{props.created_by_operator?.username}
|
||||
</Typography>
|
||||
{props.creation_time &&
|
||||
<Typography color="textSecondary" style={{fontSize: "0.75rem"}}>
|
||||
Created at: {toLocalTime(props.creation_time, props.me?.user?.view_utc_time)}
|
||||
</Typography>
|
||||
}
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{(props.scopes || []).join(", ")}
|
||||
</MythicStyledTableCell>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import React from 'react';
|
||||
import {Button, DialogContent, DialogTitle, Link, TextField} from '@mui/material';
|
||||
import {Button, DialogContent, DialogTitle, TextField} from '@mui/material';
|
||||
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 TableRow from '@mui/material/TableRow';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import Box from '@mui/material/Box';
|
||||
@@ -33,7 +37,10 @@ import {gql, useMutation} from '@apollo/client';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import {copyStringToClipboard} from "../../utilities/Clipboard";
|
||||
import {useMythicLazyQuery} from "../../utilities/useMythicLazyQuery";
|
||||
import {MythicDataGrid} from "../../MythicComponents/MythicDataGrid";
|
||||
import {MythicTableEmptyState, MythicTableErrorState, MythicTableLoadingState} from "../../MythicComponents/MythicStateDisplay";
|
||||
import {MythicClientSideTablePagination, useMythicClientPagination} from "../../MythicComponents/MythicTablePagination";
|
||||
import {APITokenRow} from "./SettingsOperatorAPITokenRow";
|
||||
import {MythicSectionHeader} from "../../MythicComponents/MythicPageHeader";
|
||||
import {
|
||||
MythicDialogBody,
|
||||
MythicDialogButton,
|
||||
@@ -118,12 +125,10 @@ export function SettingsOperatorTableRow(props){
|
||||
const [openSecretsConfig, setOpenSecretsConfig] = React.useState(false);
|
||||
const [showDeleted, setShowDeleted] = React.useState(false);
|
||||
const [apiTokens, setAPITokens] = React.useState([]);
|
||||
const queryAPITokensSuccess = (data) => {
|
||||
setAPITokens(data.apitokens);
|
||||
}
|
||||
const queryAPITokens = useMythicLazyQuery(GetAPITokens, {
|
||||
fetchPolicy: "no-cache"
|
||||
});
|
||||
const [apiTokensLoading, setAPITokensLoading] = React.useState(false);
|
||||
const [apiTokensError, setAPITokensError] = React.useState("");
|
||||
const apiTokenQueryOptions = React.useMemo(() => ({fetchPolicy: "no-cache"}), []);
|
||||
const queryAPITokens = useMythicLazyQuery(GetAPITokens, apiTokenQueryOptions);
|
||||
const [createAPIToken] = useMutation(createAPITokenMutation, {
|
||||
onCompleted: (data) => {
|
||||
if(data.createAPIToken.status === "success"){
|
||||
@@ -218,11 +223,30 @@ export function SettingsOperatorTableRow(props){
|
||||
toggleAPITokenActive({variables:{id, active}})
|
||||
}
|
||||
React.useEffect( () => {
|
||||
let active = true;
|
||||
if(open){
|
||||
setAPITokensLoading(true);
|
||||
setAPITokensError("");
|
||||
queryAPITokens({variables: {operator_id: props.id}})
|
||||
.then(({data}) => queryAPITokensSuccess(data)).catch(({data}) => console.log(data));
|
||||
.then(({data}) => {
|
||||
if(active){
|
||||
setAPITokens(data.apitokens);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.log(error);
|
||||
if(active){
|
||||
setAPITokensError("Unable to load API tokens for this operator.");
|
||||
}
|
||||
}).finally(() => {
|
||||
if(active){
|
||||
setAPITokensLoading(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [open]);
|
||||
return () => {
|
||||
active = false;
|
||||
}
|
||||
}, [open, props.id, queryAPITokens]);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TableRow key={props.id}>
|
||||
@@ -366,11 +390,11 @@ export function SettingsOperatorTableRow(props){
|
||||
<MythicStyledTableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={10}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box margin={1}>
|
||||
<div className="mythic-detail-section-header">
|
||||
<Typography component="div" className="mythic-detail-section-title">
|
||||
API Tokens
|
||||
</Typography>
|
||||
<div className="mythic-detail-section-actions">
|
||||
<MythicSectionHeader
|
||||
dense
|
||||
title="API Tokens"
|
||||
actions={
|
||||
<>
|
||||
{showDeleted ? (
|
||||
<MythicStyledTooltip title={"Hide API Tokens"}>
|
||||
<IconButton className="mythic-dialog-title-action" size="small" onClick={() => setShowDeleted(!showDeleted)}><VisibilityIcon fontSize="small" /></IconButton>
|
||||
@@ -390,8 +414,9 @@ export function SettingsOperatorTableRow(props){
|
||||
>
|
||||
API Token
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{openNewAPIToken &&
|
||||
<MythicDialog open={openNewAPIToken}
|
||||
fullWidth={true}
|
||||
@@ -411,7 +436,15 @@ export function SettingsOperatorTableRow(props){
|
||||
/>}
|
||||
/>
|
||||
}
|
||||
<APITokens me={me} apiTokens={apiTokens} onDeleteAPIToken={onDeleteAPIToken} onToggleActive={onToggleActive} showDeleted={showDeleted} />
|
||||
<APITokens
|
||||
error={apiTokensError}
|
||||
loading={apiTokensLoading}
|
||||
me={me}
|
||||
apiTokens={apiTokens}
|
||||
onDeleteAPIToken={onDeleteAPIToken}
|
||||
onToggleActive={onToggleActive}
|
||||
showDeleted={showDeleted}
|
||||
/>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</MythicStyledTableCell>
|
||||
@@ -420,98 +453,6 @@ export function SettingsOperatorTableRow(props){
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
const columns = [
|
||||
{ field: 'deleted',
|
||||
headerName: 'Delete',
|
||||
width: 60,
|
||||
renderCell: (params) => (
|
||||
params.row.deleted ? null : (
|
||||
<IconButton className="mythic-table-row-icon-action mythic-table-row-icon-action-hover-danger" size="small" onClick={() => {params.row.onDeleteAPIToken(params.row.id)}}>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)
|
||||
),
|
||||
valueGetter: (value, row) => row.deleted
|
||||
},
|
||||
{
|
||||
field: 'active',
|
||||
headerName: 'Active',
|
||||
width: 80,
|
||||
valueGetter: (value, row) => row.active,
|
||||
renderCell: (params) => (
|
||||
<Switch
|
||||
color={ "info"}
|
||||
disabled={params.row.deleted}
|
||||
checked={params.row.active}
|
||||
onChange={() => {params.row.onToggleActive(params.row.id, !params.row.active)}}
|
||||
inputProps={{ 'aria-label': 'info checkbox' }}
|
||||
name="active"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
field: 'created_by',
|
||||
headerName: 'Created By',
|
||||
flex: 1,
|
||||
valueGetter: (value, row) => row.created_by_operator?.username,
|
||||
renderCell: (params) => (
|
||||
<>
|
||||
<Typography>
|
||||
{params.row?.created_by_operator?.username}
|
||||
</Typography>
|
||||
<Typography style={{fontSize: params.row?.theme?.typography.pxToRem(12),}}>
|
||||
Created at: {toLocalTime(params.row?.creation_time, params.row.me?.user?.view_utc_time )}
|
||||
</Typography>
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
field: 'scopes',
|
||||
headerName: 'Scopes',
|
||||
flex: 1,
|
||||
valueGetter: (value, row) => (row.scopes || []).join(", "),
|
||||
renderCell: (params) => (
|
||||
<Typography>
|
||||
{(params.row.scopes || []).join(", ")}
|
||||
</Typography>
|
||||
)
|
||||
},
|
||||
{
|
||||
field: 'token_type',
|
||||
headerName: 'Type',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
headerName: 'Name',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: 'eventstepinstance',
|
||||
headerName: 'Eventing Usage',
|
||||
flex: 1,
|
||||
renderCell: (params) => (
|
||||
params.row.eventstepinstance &&
|
||||
<>
|
||||
<Typography>
|
||||
{params.row.eventstepinstance?.eventgroupinstance?.eventgroup?.name}{" / "}
|
||||
<Link target={"_blank"} color="textPrimary" underline="always"
|
||||
href={'/new/eventing?eventgroup=' +
|
||||
params.row?.eventstepinstance?.eventgroupinstance?.eventgroup?.id +
|
||||
"&eventgroupinstance=" + params.row?.eventstepinstance?.eventgroupinstance?.id
|
||||
}>
|
||||
{ params.row.eventstepinstance?.eventstep?.name}{" (" + params.row?.eventstepinstance?.eventgroupinstance?.id + ")"}
|
||||
</Link>
|
||||
</Typography>
|
||||
</>
|
||||
|
||||
),
|
||||
sortable: false,
|
||||
valueGetter: (value, row) => {
|
||||
return row.eventstepinstance?.eventgroupinstance?.eventgroup?.name
|
||||
}
|
||||
},
|
||||
];
|
||||
const APITokenValueDialog = ({tokenValue, onClose}) => {
|
||||
const onCopyTokenValue = () => {
|
||||
let success = copyStringToClipboard(tokenValue);
|
||||
@@ -549,36 +490,76 @@ const APITokenValueDialog = ({tokenValue, onClose}) => {
|
||||
</>
|
||||
);
|
||||
}
|
||||
const APITokens = ({apiTokens, onDeleteAPIToken, onToggleActive, showDeleted, me}) => {
|
||||
const theme = useTheme();
|
||||
const [data, setData] = React.useState([]);
|
||||
React.useEffect( () => {
|
||||
setData(apiTokens.reduce( (prev, c) => {
|
||||
if(showDeleted || (!showDeleted && !c.deleted)){
|
||||
return [...prev, {...c, onDeleteAPIToken: onDeleteAPIToken, onToggleActive: onToggleActive,
|
||||
me: me, theme: theme
|
||||
}];
|
||||
}
|
||||
return [...prev];
|
||||
}, []));
|
||||
const APITokens = ({apiTokens, error, loading, onDeleteAPIToken, onToggleActive, showDeleted, me}) => {
|
||||
const data = React.useMemo(() => {
|
||||
return apiTokens
|
||||
.filter((token) => showDeleted || !token.deleted)
|
||||
.sort((left, right) => right.id - left.id);
|
||||
}, [apiTokens, showDeleted]);
|
||||
return (
|
||||
<div style={{display: "flex", flexDirection: "column", width: "100%", height: "calc(30vh)", minHeight: 0}}>
|
||||
<MythicDataGrid
|
||||
rows={data}
|
||||
columns={columns}
|
||||
initialState={{
|
||||
pagination: {
|
||||
paginationModel: {
|
||||
},
|
||||
},
|
||||
sorting: {
|
||||
sortModel: [{ field: 'id', sort: 'desc' }],
|
||||
},
|
||||
}}
|
||||
autoPageSize
|
||||
/>
|
||||
</div>
|
||||
const pagination = useMythicClientPagination({
|
||||
items: data,
|
||||
resetKey: showDeleted ? "show-deleted" : "hide-deleted",
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer className="mythicElement mythic-dialog-table-wrap mythic-fixed-row-table-wrap" style={{height: "calc(30vh)", minHeight: "12rem", overflowY: "auto"}}>
|
||||
<Table stickyHeader size="small" style={{height: "auto"}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell style={{width: "3rem"}} />
|
||||
<MythicStyledTableCell style={{width: "5rem"}}>Active</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Created By</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Scopes</MythicStyledTableCell>
|
||||
<MythicStyledTableCell style={{width: "9rem"}}>Type</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Name</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Eventing Usage</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<MythicTableLoadingState
|
||||
colSpan={7}
|
||||
columns={7}
|
||||
compact
|
||||
minHeight={86}
|
||||
rows={3}
|
||||
title="Loading API tokens"
|
||||
description="Fetching tokens for this operator."
|
||||
/>
|
||||
) : error ? (
|
||||
<MythicTableErrorState
|
||||
colSpan={7}
|
||||
compact
|
||||
minHeight={150}
|
||||
title="Unable to load API tokens"
|
||||
description={error}
|
||||
/>
|
||||
) : data.length === 0 ? (
|
||||
<MythicTableEmptyState
|
||||
colSpan={7}
|
||||
compact
|
||||
minHeight={160}
|
||||
title="No API tokens"
|
||||
description={showDeleted ? "This operator does not have any API tokens yet." : "This operator does not have any active API tokens."}
|
||||
/>
|
||||
) : (
|
||||
pagination.pageData.map((token) => (
|
||||
<APITokenRow
|
||||
key={"api-token" + token.id}
|
||||
{...token}
|
||||
me={me}
|
||||
onDeleteAPIToken={onDeleteAPIToken}
|
||||
onToggleActive={onToggleActive}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{!loading && !error &&
|
||||
<MythicClientSideTablePagination pagination={pagination} />
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -374,6 +374,12 @@ tspan {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.MuiTableContainer-root.mythic-fixed-row-table-wrap {
|
||||
display: block !important;
|
||||
}
|
||||
.MuiTableContainer-root.mythic-fixed-row-table-wrap > .MuiTable-root {
|
||||
height: auto !important;
|
||||
}
|
||||
// gets the footer of the table
|
||||
.MuiPaper-root > .MuiBox-root > * {
|
||||
margin: 0;
|
||||
@@ -387,9 +393,6 @@ tspan {
|
||||
background-color: ${(props) => props.theme.table?.selectedHierarchy || props.theme.selectedCallbackHierarchyColor + "CC"};
|
||||
}
|
||||
|
||||
.MuiDataGrid-row.Mui-selected {
|
||||
background-color: ${(props) => props.theme.table?.selected || props.theme.selectedCallbackColor + "CC"} !important;
|
||||
}
|
||||
.roundedBottomCorners {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
@@ -1087,6 +1090,12 @@ tspan {
|
||||
.mythic-form .MuiTextField-root {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.mythic-form-field-control .MuiFormControl-root:has(> .MuiInputLabel-root),
|
||||
.mythic-form-field-control .MuiTextField-root:has(> .MuiInputLabel-root),
|
||||
.mythic-form .MuiFormControl-root:has(> .MuiInputLabel-root),
|
||||
.mythic-form .MuiTextField-root:has(> .MuiInputLabel-root) {
|
||||
margin-top: 0.45rem !important;
|
||||
}
|
||||
.mythic-form-field-control .MuiInputBase-root,
|
||||
.mythic-form .MuiInputBase-root {
|
||||
min-height: 38px;
|
||||
@@ -1115,6 +1124,357 @@ tspan {
|
||||
.mythic-form-switch-control {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.mythic-create-flow-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
.mythic-create-flow-content {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-create-flow-footer {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.mythic-create-selection-grid {
|
||||
display: grid;
|
||||
flex: 0 0 auto;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-builder-split {
|
||||
display: grid;
|
||||
flex: 1 1 auto;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: minmax(15rem, 0.34fr) minmax(0, 1fr);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-create-builder-split-three {
|
||||
grid-template-columns: minmax(14rem, 0.3fr) minmax(14rem, 0.3fr) minmax(0, 0.4fr);
|
||||
}
|
||||
.mythic-create-section {
|
||||
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.paper};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.65rem;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.mythic-create-section-fill {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.mythic-create-section-plain {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.mythic-create-section-scroll {
|
||||
overflow: auto;
|
||||
}
|
||||
.mythic-create-section-header {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-section-title {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.88rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.mythic-create-section-description {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.76rem;
|
||||
line-height: 1.35;
|
||||
margin-top: 0.12rem;
|
||||
}
|
||||
.mythic-create-section-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.mythic-create-section .MuiFormControl-root,
|
||||
.mythic-create-section .MuiTextField-root {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.mythic-create-section .MuiFormControl-root:has(> .MuiInputLabel-root),
|
||||
.mythic-create-section .MuiTextField-root:has(> .MuiInputLabel-root) {
|
||||
margin-top: 0.45rem !important;
|
||||
}
|
||||
.mythic-create-select {
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-create-agent-summary {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-subsection {
|
||||
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;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
padding: 0.65rem;
|
||||
}
|
||||
.mythic-create-subsection-fill {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-create-subsection-scroll {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
.mythic-create-agent-icon {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.025)"};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
flex: 0 0 4.75rem;
|
||||
height: 4.75rem;
|
||||
justify-content: center;
|
||||
padding: 0.35rem;
|
||||
}
|
||||
.mythic-create-agent-icon svg,
|
||||
.mythic-create-agent-icon img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.mythic-create-meta-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-meta-label {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
display: block;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 750;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.mythic-create-meta-value {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.35;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-create-parameter-scroll {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-create-parameter-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-parameter-group-header {
|
||||
background-image: ${(props) => {
|
||||
const headerTextColor = props.theme.pageHeaderText?.main || props.theme.palette.text.primary;
|
||||
return `linear-gradient(90deg, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)} 0%, ${alpha(headerTextColor, props.theme.palette.mode === "dark" ? 0.055 : 0.04)} 100%)`;
|
||||
}};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.pageHeaderText?.main || props.theme.palette.text.primary};
|
||||
font-size: 0.82rem;
|
||||
font-weight: 800;
|
||||
padding: 0.45rem 0.65rem;
|
||||
}
|
||||
.mythic-create-summary-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-summary-group-header {
|
||||
background-image: ${(props) => {
|
||||
const headerTextColor = props.theme.pageHeaderText?.main || props.theme.palette.text.primary;
|
||||
return `linear-gradient(90deg, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)} 0%, ${alpha(headerTextColor, props.theme.palette.mode === "dark" ? 0.055 : 0.04)} 100%)`;
|
||||
}};
|
||||
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};
|
||||
font-size: 0.82rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
padding: 0.45rem 0.65rem;
|
||||
}
|
||||
.mythic-create-summary-row {
|
||||
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
padding: 0.45rem 0.1rem 0.5rem;
|
||||
}
|
||||
.mythic-create-summary-row:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.mythic-create-summary-name {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.mythic-create-summary-value {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.76rem;
|
||||
line-height: 1.35;
|
||||
margin-top: 0.18rem;
|
||||
overflow-wrap: anywhere;
|
||||
padding-left: 0.65rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.mythic-create-parameter-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-parameter-card {
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-left: 4px solid transparent;
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: minmax(13rem, 0.32fr) minmax(0, 1fr);
|
||||
min-width: 0;
|
||||
padding: 0.7rem 0.75rem;
|
||||
}
|
||||
.mythic-create-parameter-card-modified {
|
||||
border-left-color: ${(props) => props.theme.palette.warning.main};
|
||||
}
|
||||
.mythic-create-parameter-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-parameter-title-row {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-parameter-title {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.84rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-parameter-description {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.38;
|
||||
margin-top: 0.24rem;
|
||||
}
|
||||
.mythic-create-parameter-chips {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem;
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
.mythic-create-parameter-chip {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.055)" : "rgba(0,0,0,0.035)"};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
padding: 0.22rem 0.38rem;
|
||||
}
|
||||
.mythic-create-parameter-chip-required {
|
||||
border-color: ${(props) => alpha(props.theme.palette.error.main, props.theme.palette.mode === "dark" ? 0.5 : 0.35)};
|
||||
color: ${(props) => props.theme.palette.error.main};
|
||||
}
|
||||
.mythic-create-parameter-chip-modified {
|
||||
border-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.56 : 0.4)};
|
||||
color: ${(props) => props.theme.palette.warning.main};
|
||||
}
|
||||
.mythic-create-parameter-control {
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
}
|
||||
.mythic-create-parameter-control .MuiFormControl-root,
|
||||
.mythic-create-parameter-control .MuiTextField-root,
|
||||
.mythic-create-parameter-control .MythicTextField-root {
|
||||
margin: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-create-parameter-control .MuiFormControl-root:has(> .MuiInputLabel-root),
|
||||
.mythic-create-parameter-control .MuiTextField-root:has(> .MuiInputLabel-root) {
|
||||
margin-top: 0.45rem !important;
|
||||
}
|
||||
.mythic-create-inline-control {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-create-inline-control > .MuiFormControl-root {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-choice-divider {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
.mythic-create-array-table.MuiTableContainer-root {
|
||||
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
}
|
||||
.mythic-create-array-table .MuiTableCell-root {
|
||||
padding: 0.35rem 0.45rem !important;
|
||||
}
|
||||
.mythic-create-dictionary-row {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: 2rem minmax(8rem, 0.28fr) minmax(0, 1fr);
|
||||
margin-bottom: 0.45rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-create-dictionary-add {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.mythic-create-builder-split,
|
||||
.mythic-create-builder-split-three,
|
||||
.mythic-create-parameter-card {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.mythic-dashboard-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1440,6 +1800,10 @@ tspan {
|
||||
.mythic-dialog-body .MuiTextField-root {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.mythic-dialog-body .MuiFormControl-root:has(> .MuiInputLabel-root),
|
||||
.mythic-dialog-body .MuiTextField-root:has(> .MuiInputLabel-root) {
|
||||
margin-top: 0.45rem !important;
|
||||
}
|
||||
.mythic-table-bulk-actions {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
@@ -1902,6 +2266,10 @@ tspan {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-wizard .MuiFormControl-root:has(> .MuiInputLabel-root),
|
||||
.mythic-eventing-wizard .MuiTextField-root:has(> .MuiInputLabel-root) {
|
||||
margin-top: 0.45rem !important;
|
||||
}
|
||||
.mythic-eventing-wizard-header {
|
||||
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
padding: 0.75rem 1rem;
|
||||
@@ -2093,43 +2461,14 @@ tspan {
|
||||
min-height: 1.65rem;
|
||||
padding: 0 0.55rem;
|
||||
}
|
||||
.mythic-eventing-metadata-trigger-table {
|
||||
border-collapse: separate !important;
|
||||
border-spacing: 0 0.4rem !important;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
.mythic-eventing-trigger-parameter-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-metadata-trigger-table > .MuiTableHead-root {
|
||||
display: none;
|
||||
}
|
||||
.mythic-eventing-metadata-trigger-table > .MuiTableBody-root > .MuiTableRow-root > .MuiTableCell-root {
|
||||
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
border-top: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
padding: 0.5rem 0.6rem !important;
|
||||
}
|
||||
.mythic-eventing-metadata-trigger-table > .MuiTableBody-root > .MuiTableRow-root > .MuiTableCell-root:first-of-type {
|
||||
border-left: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px 0 0 ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.72rem;
|
||||
font-weight: 850;
|
||||
vertical-align: top;
|
||||
width: 38%;
|
||||
}
|
||||
.mythic-eventing-metadata-trigger-table > .MuiTableBody-root > .MuiTableRow-root > .MuiTableCell-root:last-of-type {
|
||||
border-radius: 0 ${(props) => props.theme.shape.borderRadius}px ${(props) => props.theme.shape.borderRadius}px 0;
|
||||
border-right: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
width: 62%;
|
||||
}
|
||||
.mythic-eventing-metadata-trigger-table .MuiTable-root:not(.mythic-eventing-metadata-trigger-table) .MuiTableCell-root {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.mythic-eventing-metadata-trigger-table .MuiTable-root:not(.mythic-eventing-metadata-trigger-table) .MuiTableCell-root:first-of-type {
|
||||
padding-right: 0.4rem !important;
|
||||
width: 2.5rem !important;
|
||||
}
|
||||
.mythic-eventing-metadata-trigger-table .MuiTable-root:not(.mythic-eventing-metadata-trigger-table) .MuiTableCell-root:first-of-type + .MuiTableCell-root {
|
||||
padding-left: 0.4rem !important;
|
||||
.mythic-eventing-trigger-parameter-list .mythic-create-parameter-card {
|
||||
grid-template-columns: minmax(14rem, 0.36fr) minmax(0, 1fr);
|
||||
}
|
||||
.mythic-eventing-choice-row {
|
||||
align-items: flex-start;
|
||||
@@ -2137,6 +2476,15 @@ tspan {
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||
}
|
||||
.mythic-eventing-choice-row > .MuiFormControl-root,
|
||||
.mythic-eventing-choice-row > .mythic-eventing-choice-custom {
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
.mythic-eventing-choice-row > .MuiFormControl-root .MuiTextField-root,
|
||||
.mythic-eventing-choice-row > .mythic-eventing-choice-custom .MuiFormControl-root,
|
||||
.mythic-eventing-choice-row > .mythic-eventing-choice-custom .MuiTextField-root {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.mythic-eventing-choice-separator {
|
||||
align-items: center;
|
||||
align-self: center;
|
||||
@@ -2406,7 +2754,7 @@ tspan {
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-eventing-step-config-card-modern {
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.default};
|
||||
}
|
||||
.mythic-eventing-step-config-summary {
|
||||
align-items: flex-start;
|
||||
@@ -2490,14 +2838,18 @@ tspan {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.mythic-eventing-step-config-section {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.018)" : "rgba(0,0,0,0.01)"};
|
||||
background-color: ${(props) => props.theme.surfaces?.raised || props.theme.palette.background.paper};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
box-shadow: 0 1px 2px ${(props) => alpha(props.theme.palette.common.black, props.theme.palette.mode === "dark" ? 0.18 : 0.06)};
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-eventing-step-config-section-header {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.025)" : "rgba(0,0,0,0.012)"};
|
||||
background-image: ${(props) => {
|
||||
const headerTextColor = props.theme.pageHeaderText?.main || props.theme.palette.text.primary;
|
||||
return `linear-gradient(90deg, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.12 : 0.07)} 0%, ${alpha(headerTextColor, props.theme.palette.mode === "dark" ? 0.045 : 0.035)} 100%)`;
|
||||
}};
|
||||
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
padding: 0.62rem 0.7rem 0.55rem;
|
||||
}
|
||||
@@ -2517,13 +2869,18 @@ tspan {
|
||||
.mythic-eventing-step-config-section-body {
|
||||
padding: 0.7rem;
|
||||
}
|
||||
.mythic-eventing-step-section-grid,
|
||||
.mythic-eventing-step-field-grid {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-step-section-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-step-config-section-wide {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -2581,7 +2938,7 @@ tspan {
|
||||
}
|
||||
.mythic-eventing-step-list-item {
|
||||
align-items: flex-start;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.02)" : "rgba(0,0,0,0.012)"};
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.026)" : "rgba(0,0,0,0.018)"};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
@@ -2590,32 +2947,59 @@ tspan {
|
||||
padding: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-eventing-step-list-item-editable {
|
||||
display: block;
|
||||
}
|
||||
.mythic-eventing-step-list-item > .MuiIconButton-root {
|
||||
flex: 0 0 auto;
|
||||
margin-top: 0.15rem;
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
.mythic-eventing-step-list-content {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-step-input-grid {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: minmax(9rem, 0.75fr) minmax(10rem, 0.8fr) minmax(14rem, 1.45fr);
|
||||
grid-template-columns: 2rem minmax(9rem, 0.75fr) minmax(10rem, 0.8fr) minmax(14rem, 1.45fr);
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-step-output-grid {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: minmax(9rem, 0.75fr) minmax(14rem, 1.5fr);
|
||||
grid-template-columns: 2rem minmax(9rem, 0.75fr) minmax(14rem, 1.5fr);
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-step-list-item-editable .mythic-eventing-step-row-action {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
margin-top: 0.45rem !important;
|
||||
}
|
||||
.mythic-eventing-step-input-grid > :not(.mythic-eventing-step-row-action):not(.mythic-eventing-step-helper-text),
|
||||
.mythic-eventing-step-output-grid > :not(.mythic-eventing-step-row-action):not(.mythic-eventing-step-helper-text) {
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
.mythic-eventing-step-input-grid > :not(.mythic-eventing-step-row-action):not(.mythic-eventing-step-helper-text) .MuiFormControl-root,
|
||||
.mythic-eventing-step-input-grid > :not(.mythic-eventing-step-row-action):not(.mythic-eventing-step-helper-text) .MuiTextField-root,
|
||||
.mythic-eventing-step-output-grid > :not(.mythic-eventing-step-row-action):not(.mythic-eventing-step-helper-text) .MuiFormControl-root,
|
||||
.mythic-eventing-step-output-grid > :not(.mythic-eventing-step-row-action):not(.mythic-eventing-step-helper-text) .MuiTextField-root {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.mythic-eventing-step-list-item-editable .mythic-eventing-choice-row {
|
||||
align-items: center;
|
||||
}
|
||||
.mythic-eventing-step-list-item-editable .mythic-eventing-choice-separator {
|
||||
align-self: center;
|
||||
}
|
||||
.mythic-eventing-step-helper-text {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
grid-column: 2 / -1;
|
||||
line-height: 1.35;
|
||||
margin-top: 0.4rem;
|
||||
margin-top: -0.05rem;
|
||||
}
|
||||
.mythic-eventing-step-empty-inline {
|
||||
align-items: center;
|
||||
@@ -2632,7 +3016,7 @@ tspan {
|
||||
.mythic-eventing-step-action-data {
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-step-action-data > .MuiTypography-root {
|
||||
.mythic-eventing-step-help-text {
|
||||
background-color: ${(props) => props.theme.palette.info.main + "10"};
|
||||
border: 1px solid ${(props) => props.theme.palette.info.main + "2f"};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
@@ -2643,47 +3027,76 @@ tspan {
|
||||
margin: 0 0 0.65rem;
|
||||
padding: 0.55rem 0.65rem;
|
||||
}
|
||||
.mythic-eventing-step-action-data > .MuiTable-root {
|
||||
border-collapse: separate !important;
|
||||
border-spacing: 0 0.5rem !important;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
.mythic-eventing-action-data-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-step-action-data > .MuiTable-root > .MuiTableHead-root {
|
||||
display: none;
|
||||
.mythic-eventing-action-data-card {
|
||||
background-color: ${(props) => props.theme.palette.background.paper};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-left: 4px solid transparent;
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: minmax(13rem, 0.34fr) minmax(0, 1fr);
|
||||
min-width: 0;
|
||||
padding: 0.7rem 0.75rem;
|
||||
}
|
||||
.mythic-eventing-step-action-data > .MuiTable-root > .MuiTableBody-root > .MuiTableRow-root {
|
||||
background-color: transparent;
|
||||
.mythic-eventing-action-data-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-step-action-data > .MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > .MuiTableCell-root {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.018)" : "rgba(0,0,0,0.01)"};
|
||||
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
border-top: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
padding: 0.62rem 0.7rem !important;
|
||||
vertical-align: top;
|
||||
.mythic-eventing-action-data-title-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.mythic-eventing-step-action-data > .MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > .MuiTableCell-root:first-of-type {
|
||||
border-left: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px 0 0 ${(props) => props.theme.shape.borderRadius}px;
|
||||
width: 34%;
|
||||
}
|
||||
.mythic-eventing-step-action-data > .MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > .MuiTableCell-root:last-of-type {
|
||||
border-radius: 0 ${(props) => props.theme.shape.borderRadius}px ${(props) => props.theme.shape.borderRadius}px 0;
|
||||
border-right: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
width: 66%;
|
||||
}
|
||||
.mythic-eventing-step-action-data > .MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > .MuiTableCell-root:first-of-type .MuiTypography-root:first-of-type {
|
||||
.mythic-eventing-action-data-title {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.76rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 850 !important;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.mythic-eventing-step-action-data > .MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > .MuiTableCell-root:first-of-type .MuiTypography-root:not(:first-of-type) {
|
||||
.mythic-eventing-action-data-chip {
|
||||
background-color: ${(props) => props.theme.palette.warning.main + "16"};
|
||||
border: 1px solid ${(props) => props.theme.palette.warning.main + "45"};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.warning.main};
|
||||
display: inline-flex;
|
||||
font-size: 0.64rem;
|
||||
font-weight: 850;
|
||||
line-height: 1;
|
||||
min-height: 1.2rem;
|
||||
padding: 0 0.35rem;
|
||||
}
|
||||
.mythic-eventing-action-data-description {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
margin-top: 0.2rem;
|
||||
margin-top: 0.28rem;
|
||||
}
|
||||
.mythic-eventing-action-data-control {
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-action-data-control .MuiFormControl-root,
|
||||
.mythic-eventing-action-data-control .MuiTextField-root {
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-eventing-action-array-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-action-array-row {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: 2rem minmax(0, 1fr);
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-eventing-step-config-card .mythic-eventing-wizard-table {
|
||||
border-spacing: 0 !important;
|
||||
@@ -2697,11 +3110,14 @@ tspan {
|
||||
border-right: 0 !important;
|
||||
border-top: 0 !important;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.mythic-eventing-step-input-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.mythic-eventing-step-input-grid {
|
||||
grid-template-columns: 2rem minmax(0, 1fr);
|
||||
}
|
||||
.mythic-eventing-step-input-grid > :not(.mythic-eventing-step-row-action):not(.mythic-eventing-step-helper-text) {
|
||||
grid-column: 2;
|
||||
}
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.mythic-eventing-step-config-summary,
|
||||
.mythic-eventing-step-config-summary-actions {
|
||||
@@ -2711,12 +3127,20 @@ tspan {
|
||||
.mythic-eventing-step-switch-row {
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-eventing-step-section-grid,
|
||||
.mythic-eventing-step-field-grid,
|
||||
.mythic-eventing-step-output-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.mythic-eventing-step-field-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.mythic-eventing-step-output-grid {
|
||||
grid-template-columns: 2rem minmax(0, 1fr);
|
||||
}
|
||||
.mythic-eventing-step-output-grid > :not(.mythic-eventing-step-row-action):not(.mythic-eventing-step-helper-text) {
|
||||
grid-column: 2;
|
||||
}
|
||||
.mythic-eventing-action-data-card,
|
||||
.mythic-eventing-trigger-parameter-list .mythic-create-parameter-card {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.mythic-eventing-wizard-empty {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.82rem;
|
||||
|
||||
Reference in New Issue
Block a user