mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
dashboard upates
This commit is contained in:
@@ -5,9 +5,13 @@ import Typography from '@mui/material/Typography';
|
||||
import {useNavigate} from 'react-router-dom';
|
||||
import {getStringSize} from "../Callbacks/ResponseDisplayTable";
|
||||
import {getSkewedNow} from "../../utilities/Time";
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import ToggleButton from '@mui/material/ToggleButton';
|
||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||
import Popover from '@mui/material/Popover';
|
||||
import {CallbackDataCard, DashboardEmptyCard, GaugeCard, LineTimeMultiChartCard, PieChartCard, TableDataCard} from './DashboardComponents';
|
||||
import {MythicAgentSVGIcon} from "../../MythicComponents/MythicAgentSVGIcon";
|
||||
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
|
||||
@@ -41,7 +45,6 @@ import {GetMythicSetting, useSetMythicSetting} from "../../MythicComponents/Myth
|
||||
import FormatListBulletedAddIcon from '@mui/icons-material/FormatListBulletedAdd';
|
||||
import AddchartIcon from '@mui/icons-material/Addchart';
|
||||
import PlaylistRemoveIcon from '@mui/icons-material/PlaylistRemove';
|
||||
import {MythicSelectFromListDialog} from "../../MythicComponents/MythicSelectFromListDialog";
|
||||
import {PreviewFileMediaDialog} from "../../MythicComponents/PreviewFileMedia";
|
||||
import {faPhotoVideo} from '@fortawesome/free-solid-svg-icons';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
@@ -60,6 +63,7 @@ import {ImageWithAuth} from "../../utilities/ImageWithAuth";
|
||||
import {MythicPageHeader, MythicPageHeaderChip} from "../../MythicComponents/MythicPageHeader";
|
||||
import {MythicStatusChip} from "../../MythicComponents/MythicStatusChip";
|
||||
import {MythicLoadingState} from "../../MythicComponents/MythicStateDisplay";
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
|
||||
const LeadDashboardQuery = gql`
|
||||
${taskingDataFragment}
|
||||
@@ -236,6 +240,74 @@ const errorColors = [
|
||||
];
|
||||
|
||||
const dashboardOptions = ["operator", "lead", "custom"];
|
||||
const dashboardElementGridSpans = {
|
||||
"Activity Timeline": 2,
|
||||
"Credentials": 2,
|
||||
"Downloads": 2,
|
||||
"Eventing Status": 2,
|
||||
"My Operations": 2,
|
||||
"Payloads": 2,
|
||||
"Proxy Usage": 2,
|
||||
"Recent Screenshots": 2,
|
||||
"Screenshots": 2,
|
||||
"Tasking": 2,
|
||||
"Top 10 Active Hosts": 2,
|
||||
"Top 10 Host Contexts": 2,
|
||||
"Top 10 User Contexts": 2,
|
||||
};
|
||||
const getDashboardRowTemplate = (dashboardRow = []) => {
|
||||
const columnCount = dashboardRow.reduce((total, dashboardElement) => {
|
||||
return total + (dashboardElementGridSpans[dashboardElement] || 1);
|
||||
}, 0);
|
||||
return `repeat(${Math.max(1, columnCount)}, minmax(0, 1fr))`;
|
||||
};
|
||||
const ActiveCallbackRecentHoursControl = ({recentHours, onChangeRecent}) => {
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const onOpen = (event) => {
|
||||
event.stopPropagation();
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const onClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const onChange = (name, value, error) => {
|
||||
const nextValue = Math.max(1, Number(value) || 1);
|
||||
onChangeRecent(name, nextValue, error);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<MythicStyledTooltip title={`Recent check-in window: ${recentHours} ${recentHours === 1 ? "hour" : "hours"}`}>
|
||||
<IconButton className="mythic-dashboard-icon-button mythic-dashboard-icon-button-hover-info" onClick={onOpen} size="small">
|
||||
<TuneIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<Popover
|
||||
anchorEl={anchorEl}
|
||||
anchorOrigin={{vertical: "bottom", horizontal: "right"}}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
transformOrigin={{vertical: "top", horizontal: "right"}}
|
||||
>
|
||||
<div className="mythic-dashboard-widget-settings-popover">
|
||||
<div className="mythic-dashboard-widget-settings-title">Recent callback window</div>
|
||||
<div className="mythic-dashboard-widget-settings-copy">
|
||||
Count callbacks that checked in within this many hours.
|
||||
</div>
|
||||
<MythicTextField
|
||||
type="number"
|
||||
value={recentHours}
|
||||
name="Recent hours"
|
||||
onChange={onChange}
|
||||
showLabel={true}
|
||||
marginBottom="0px"
|
||||
width={9}
|
||||
/>
|
||||
</div>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const ActiveCallbacksDashboardElement = ({me, data, editing, removeElement}) => {
|
||||
const [recentHours, setRecentHours] = React.useState(1);
|
||||
const navigate = useNavigate();
|
||||
@@ -261,41 +333,30 @@ const ActiveCallbacksDashboardElement = ({me, data, editing, removeElement}) =>
|
||||
}
|
||||
return prev;
|
||||
}, 0);
|
||||
setActive({"active": newActive, "recent": recent, "total": data?.callback?.length});
|
||||
setActive({"active": newActive || 0, "recent": recent, "total": data?.callback?.length || 0});
|
||||
}, [data, recentHours]);
|
||||
const onChangeRecent = (name, value, error) => {
|
||||
setRecentHours(value);
|
||||
}
|
||||
const activeRatio = active.total > 0 ? active.active / active.total : 0;
|
||||
const activeStatus = active.total === 0 ? "neutral" : activeRatio > 0.5 ? "success" : active.active > 0 ? "warning" : "danger";
|
||||
const activeStatusLabel = active.total === 0 ? "No callbacks" : active.active > 0 ? "Live callbacks" : "None active";
|
||||
return (
|
||||
<CallbackDataCard data={active}
|
||||
<CallbackDataCard
|
||||
mainTitle={
|
||||
<div className="mythic-dashboard-metric-title-row">
|
||||
{"Active Callbacks"}
|
||||
<MythicTextField type={"number"} value={recentHours} name={"recent hours"} onChange={onChangeRecent}
|
||||
showLabel={true} inline={true} marginBottom={"0px"}
|
||||
variant={"standard"}
|
||||
width={5} />
|
||||
</div>}
|
||||
secondTitle={
|
||||
<div style={{display: "flex", flexDirection: "row"}}>
|
||||
{"Recent Checkins <" + recentHours + "hrs"}
|
||||
</div>
|
||||
"Active Callbacks"
|
||||
}
|
||||
mainElement={
|
||||
<>
|
||||
<Typography className="mythic-dashboard-metric-value" variant={"h1"} onClick={() => navigate("/new/callbacks")}>
|
||||
{active.active}
|
||||
</Typography>
|
||||
<Typography className="mythic-dashboard-metric-total" variant={"h5"}>
|
||||
/ {active.total}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
secondaryElement={
|
||||
<Typography className="mythic-dashboard-metric-secondary" variant={"h2"}>
|
||||
{active.recent}
|
||||
</Typography>
|
||||
actions={
|
||||
<ActiveCallbackRecentHoursControl recentHours={recentHours} onChangeRecent={onChangeRecent} />
|
||||
}
|
||||
primaryValue={active.active}
|
||||
totalValue={active.total}
|
||||
primaryLabel="Active callbacks"
|
||||
secondaryValue={active.recent}
|
||||
secondaryLabel={`Recent check-ins under ${recentHours} ${recentHours === 1 ? "hour" : "hours"}`}
|
||||
statusLabel={activeStatusLabel}
|
||||
statusLevel={activeStatus}
|
||||
onClick={() => navigate("/new/callbacks")}
|
||||
editing={editing}
|
||||
removeElement={removeElement}
|
||||
/>
|
||||
@@ -453,10 +514,10 @@ const ProxyUsageDashboardElement = ({me, data, editing, removeElement}) => {
|
||||
callbackPortActiveArrayOptions.push({
|
||||
label: key,
|
||||
value: <>
|
||||
<Typography>
|
||||
<Typography className="mythic-dashboard-table-secondary">
|
||||
{"Tx: " + getStringSize({cellData: {"plaintext": String(value.sent)}})}
|
||||
</Typography>
|
||||
<Typography>
|
||||
<Typography className="mythic-dashboard-table-secondary">
|
||||
{"Rx: " + getStringSize({cellData: {"plaintext": String(value.received)}})}
|
||||
</Typography>
|
||||
</>,
|
||||
@@ -481,8 +542,8 @@ const ProxyUsageDashboardElement = ({me, data, editing, removeElement}) => {
|
||||
<TableBody >
|
||||
{callbackPorts.map( (d, index) => (
|
||||
<TableRow hover key={d.label + index} onClick={() => {handlePortClick(d)}} style={{cursor: "pointer"}}>
|
||||
<MythicTableCell>{d.label}</MythicTableCell>
|
||||
<MythicTableCell>{d.value}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-primary">{d.label}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-tight">{d.value}</MythicTableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
@@ -572,8 +633,8 @@ const Top10UserContextsDashboardElement = ({me, data, editing, removeElement}) =
|
||||
<TableBody >
|
||||
{taskedUser.map( (d, index) => (
|
||||
<TableRow hover key={d.label + index} onClick={() => {handleUserContextClick(d)}} style={{cursor: "pointer"}}>
|
||||
<MythicTableCell>{d.label}</MythicTableCell>
|
||||
<MythicTableCell>{d.value}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-primary">{d.label}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-count">{d.value}</MythicTableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
@@ -659,8 +720,8 @@ const Top10HostContextsDashboardElement = ({me, data, editing, removeElement}) =
|
||||
<TableBody >
|
||||
{taskedHosts.map( (d, index) => (
|
||||
<TableRow hover key={d.label + index} onClick={() => {handleHostContextClick(d)}} style={{cursor: "pointer"}}>
|
||||
<MythicTableCell>{d.label}</MythicTableCell>
|
||||
<MythicTableCell>{d.value}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-primary">{d.label}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-count">{d.value}</MythicTableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
@@ -687,29 +748,32 @@ const Top10RecentPayloadsDashboardElement = ({me, data, editing, removeElement})
|
||||
return {
|
||||
payload: p,
|
||||
id: p.uuid,
|
||||
label: <div style={{display: "flex", alignItems: "center"}}>
|
||||
label: <div className="mythic-dashboard-table-identity">
|
||||
<MythicStyledTooltip title={p.payloadtype.name} tooltipStyle={{marginRight: "5px"}}>
|
||||
<MythicAgentSVGIcon payload_type={p.payloadtype.name} style={{width: "20px", height: "20px"}} />
|
||||
</MythicStyledTooltip>
|
||||
{b64DecodeUnicode(p.filemetum.filename_text)}
|
||||
<span className="mythic-dashboard-table-primary-text">{b64DecodeUnicode(p.filemetum.filename_text)}</span>
|
||||
</div>,
|
||||
value: <>
|
||||
value: <div className="mythic-dashboard-table-actions-inline">
|
||||
<PayloadsTableRowBuildStatus {...p} />
|
||||
<MythicStyledTooltip title={"View Payload Configuration"} tooltipStyle={{marginRight: "5px"}}>
|
||||
<InfoIconOutline color={"info"} onClick={(e)=> clickDetail(e, p)} />
|
||||
<InfoIconOutline className="mythic-dashboard-table-icon-action mythic-dashboard-table-icon-action-info" onClick={(e)=> clickDetail(e, p)} />
|
||||
</MythicStyledTooltip>
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
}) || [];
|
||||
setPayloads(newPayloadData);
|
||||
}, [data]);
|
||||
if( data.payload && payloads.length === 0){
|
||||
return (
|
||||
<DashboardEmptyCard
|
||||
<TableDataCard
|
||||
title={"Recently Created Payloads"}
|
||||
editing={editing}
|
||||
removeElement={removeElement}
|
||||
title={"No Payloads Created"}
|
||||
action={
|
||||
empty={true}
|
||||
emptyTitle="No payloads created"
|
||||
emptyDescription="Build a payload to start populating this view."
|
||||
emptyAction={
|
||||
<Button
|
||||
className="mythic-dashboard-primary-button"
|
||||
onClick={() => navigate("/new/createpayload")}
|
||||
@@ -719,9 +783,7 @@ const Top10RecentPayloadsDashboardElement = ({me, data, editing, removeElement})
|
||||
Create Your First Payload
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
Build a payload to start populating this view.
|
||||
</DashboardEmptyCard>
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
@@ -739,8 +801,8 @@ const Top10RecentPayloadsDashboardElement = ({me, data, editing, removeElement})
|
||||
<TableBody >
|
||||
{payloads.map( (d, index) => (
|
||||
<TableRow hover key={d.label + index} >
|
||||
<MythicTableCell>{d.label}</MythicTableCell>
|
||||
<MythicTableCell>{d.value}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-primary">{d.label}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-actions">{d.value}</MythicTableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
@@ -789,17 +851,17 @@ const Top10RecentWorkflowsDashboardElement = ({me, data, editing, removeElement}
|
||||
return {
|
||||
entry: p,
|
||||
id: p.id,
|
||||
label: <>
|
||||
<Typography>
|
||||
label: <div className="mythic-dashboard-table-stack">
|
||||
<Typography className="mythic-dashboard-table-primary-text">
|
||||
{p.eventgroup.name}
|
||||
</Typography>
|
||||
</>,
|
||||
value: <>
|
||||
</div>,
|
||||
value: <div className="mythic-dashboard-table-actions-inline">
|
||||
<GetStatusSymbol data={p} />
|
||||
<MythicStyledTooltip title={"Open Graph in Modal"}>
|
||||
<OpenInNewTwoToneIcon color={"secondary"} onClick={(e) => clickDetail(e, p)} />
|
||||
<OpenInNewTwoToneIcon className="mythic-dashboard-table-icon-action" onClick={(e) => clickDetail(e, p)} />
|
||||
</MythicStyledTooltip>
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
}) || [];
|
||||
setWorkflows(newPayloadData);
|
||||
@@ -807,11 +869,14 @@ const Top10RecentWorkflowsDashboardElement = ({me, data, editing, removeElement}
|
||||
if( data.eventgroupinstance && workflows.length === 0){
|
||||
return (
|
||||
<>
|
||||
<DashboardEmptyCard
|
||||
<TableDataCard
|
||||
title={"Workflow Executions"}
|
||||
editing={editing}
|
||||
removeElement={removeElement}
|
||||
title={"No Workflows Created"}
|
||||
action={
|
||||
empty={true}
|
||||
emptyTitle="No workflows created"
|
||||
emptyDescription="Create a workflow to see recent eventing executions here."
|
||||
emptyAction={
|
||||
<Button
|
||||
className="mythic-dashboard-primary-button"
|
||||
onClick={() => setOpenNewWorkflowModal(true)}
|
||||
@@ -821,9 +886,7 @@ const Top10RecentWorkflowsDashboardElement = ({me, data, editing, removeElement}
|
||||
Create a Workflow
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
Create a workflow to see recent eventing executions here.
|
||||
</DashboardEmptyCard>
|
||||
/>
|
||||
{openNewWorkflowModal &&
|
||||
<MythicDialog fullWidth={true} maxWidth="xl" open={openNewWorkflowModal}
|
||||
onClose={(e) => {
|
||||
@@ -854,8 +917,8 @@ const Top10RecentWorkflowsDashboardElement = ({me, data, editing, removeElement}
|
||||
<TableBody >
|
||||
{workflows.map( (d, index) => (
|
||||
<TableRow hover key={d.label + index} onClick={() => {handleHostContextClick(d)}} style={{cursor: "pointer"}}>
|
||||
<MythicTableCell>{d.label}</MythicTableCell>
|
||||
<MythicTableCell>{d.value}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-primary">{d.label}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-actions">{d.value}</MythicTableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
@@ -1066,15 +1129,15 @@ const MyOperationsDashboardElement = ({me, data, reloadDashboard, editing, remov
|
||||
<TableBody >
|
||||
{operations.map( (d) => (
|
||||
<TableRow hover key={d.id} onClick={() => {handleHostContextClick(d)}} style={{cursor: "pointer"}}>
|
||||
<MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-primary">
|
||||
<div className="mythic-status-stack">
|
||||
<span>{d.label}</span>
|
||||
<span className="mythic-dashboard-table-primary-text">{d.label}</span>
|
||||
{d.operation.complete &&
|
||||
<MythicStatusChip label="Completed" status="completed" />
|
||||
}
|
||||
</div>
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-actions">
|
||||
<Button className="mythic-dashboard-table-action" size="small" onClick={()=>{setOpenUpdateNotifications(true);}} startIcon={<EditIcon/>}
|
||||
disabled={me?.user?.current_operation_id !== d.operation.id}
|
||||
variant="outlined">Edit</Button>
|
||||
@@ -1085,7 +1148,7 @@ const MyOperationsDashboardElement = ({me, data, reloadDashboard, editing, remov
|
||||
/>
|
||||
}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-actions">
|
||||
<Button className="mythic-dashboard-table-action" size="small" onClick={()=>{setOpenUpdateOperators(true);}}
|
||||
disabled={me?.user?.current_operation_id !== d.operation.id}
|
||||
startIcon={<AssignmentIndIcon/>} variant="outlined">Edit</Button>
|
||||
@@ -1096,7 +1159,7 @@ const MyOperationsDashboardElement = ({me, data, reloadDashboard, editing, remov
|
||||
/>
|
||||
}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-actions">
|
||||
{d.id === me.user.current_operation_id ? (
|
||||
<MythicStatusChip label="Current" status="active" />
|
||||
) : (
|
||||
@@ -1421,17 +1484,17 @@ const Top10RecentFileDownloadsDashboardElement = ({me, data, editing, removeElem
|
||||
return {
|
||||
filemeta: newFile,
|
||||
id: p.id,
|
||||
label: <div style={{display: "flex", alignItems: "center"}}>
|
||||
{newFile.host}
|
||||
label: <div className="mythic-dashboard-table-identity">
|
||||
<span className="mythic-dashboard-table-primary-text">{newFile.host}</span>
|
||||
</div>,
|
||||
value: <div style={{display: "flex", alignItems: "center"}}>
|
||||
value: <div className="mythic-dashboard-table-file-row">
|
||||
<MythicStyledTooltip title={"Preview Media"}>
|
||||
<FontAwesomeIcon icon={faPhotoVideo} style={{height: "20px", position: "relative", cursor: "pointer", display: "inline-block", marginRight: "10px"}}
|
||||
<FontAwesomeIcon className="mythic-dashboard-table-icon-action mythic-dashboard-table-icon-action-info" icon={faPhotoVideo}
|
||||
onClick={(e) => onPreviewMedia(e, newFile)} />
|
||||
</MythicStyledTooltip>
|
||||
<Link style={{wordBreak: "break-all"}} color="textPrimary" underline="always" href={"/direct/download/" + newFile.agent_file_id}>{newFile.filename_text}</Link>
|
||||
<Link className="mythic-dashboard-table-link" color="textPrimary" underline="always" href={"/direct/download/" + newFile.agent_file_id}>{newFile.filename_text}</Link>
|
||||
{!newFile.complete &&
|
||||
<Typography color="secondary" >({newFile.chunks_received} / <b>{newFile.total_chunks}</b>) Chunks</Typography>
|
||||
<Typography className="mythic-dashboard-table-secondary" color="secondary" >({newFile.chunks_received} / <b>{newFile.total_chunks}</b>) Chunks</Typography>
|
||||
}
|
||||
</div>,
|
||||
}
|
||||
@@ -1452,9 +1515,9 @@ const Top10RecentFileDownloadsDashboardElement = ({me, data, editing, removeElem
|
||||
tableBody={
|
||||
<TableBody >
|
||||
{files.map( (d, index) => (
|
||||
<TableRow hover key={d.label + index} >
|
||||
<MythicTableCell>{d.label}</MythicTableCell>
|
||||
<MythicTableCell>{d.value}</MythicTableCell>
|
||||
<TableRow hover key={d.label + index} >
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-primary">{d.label}</MythicTableCell>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-primary">{d.value}</MythicTableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
@@ -1547,6 +1610,7 @@ const Top10RecentScreenshotsDashboardElement = ({me, data, editing, removeElemen
|
||||
empty={files.length === 0}
|
||||
emptyTitle="No screenshots"
|
||||
emptyDescription="Recent screenshots will appear here once screenshot files are created."
|
||||
summary={false}
|
||||
/>
|
||||
{ openScreenshot.open &&
|
||||
<MythicDialog fullWidth={true} maxWidth="xl" open={openScreenshot.open}
|
||||
@@ -1584,14 +1648,16 @@ const Top10RecentTasksDashboardElement = ({me, data, editing, removeElement}) =>
|
||||
tableBody={
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<MythicTableCell>
|
||||
{tasks.map( (task) => (
|
||||
task.is_interactive_task ? (
|
||||
<TaskDisplayInteractiveSearch key={"taskinteractdisplay" + task.id} me={me} task={task} responsesSurrounding={5} />
|
||||
) : (
|
||||
<TaskDisplay key={"taskinteractdisplay" + task.id} me={me} task={task} command_id={task.command == null ? 0 : task.command.id} />
|
||||
)
|
||||
))}
|
||||
<MythicTableCell className="mythic-dashboard-tasking-cell">
|
||||
<div className="mythic-dashboard-tasking-list">
|
||||
{tasks.map( (task) => (
|
||||
task.is_interactive_task ? (
|
||||
<TaskDisplayInteractiveSearch key={"taskinteractdisplay" + task.id} me={me} task={task} responsesSurrounding={5} />
|
||||
) : (
|
||||
<TaskDisplay key={"taskinteractdisplay" + task.id} me={me} task={task} command_id={task.command == null ? 0 : task.command.id} />
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
</MythicTableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
@@ -1605,6 +1671,8 @@ const Top10RecentTasksDashboardElement = ({me, data, editing, removeElement}) =>
|
||||
empty={tasks.length === 0}
|
||||
emptyTitle="No recent tasking"
|
||||
emptyDescription="Recent tasks will appear here once operators issue tasking."
|
||||
summary={false}
|
||||
tableClassName="mythic-dashboard-tasking-table"
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1640,20 +1708,20 @@ const Top10RecentCredentialsDashboardElement = ({me, data, editing, removeElemen
|
||||
<TableBody>
|
||||
{credentials.map(c => (
|
||||
<TableRow key={c.id} hover>
|
||||
<MythicStyledTableCell style={{ whiteSpace: "pre-line",wordBreak: "break-all", display: "flex", flexDirection: "row"}}>
|
||||
<MythicStyledTableCell className="mythic-dashboard-table-cell-wrap mythic-dashboard-table-cell-account">
|
||||
<MythicStyledTooltip title={"Copy Credential to clipboard"}>
|
||||
<IconButton className="mythic-dashboard-icon-button" onClick={() => onCopyToClipboard(c.credential_text)} size="small">
|
||||
<FontAwesomeIcon icon={faCopy}/>
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<span>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}><b>Account: </b>{c.account}</Typography>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}><b>Realm: </b>{c.realm}</Typography>
|
||||
<Typography variant="body2" style={{wordBreak: "break-all"}}><b>Type: </b>{c.type}</Typography>
|
||||
<span className="mythic-dashboard-table-stack">
|
||||
<Typography className="mythic-dashboard-table-primary-text" variant="body2"><b>Account: </b>{c.account}</Typography>
|
||||
<Typography className="mythic-dashboard-table-secondary" variant="body2"><b>Realm: </b>{c.realm}</Typography>
|
||||
<Typography className="mythic-dashboard-table-secondary" variant="body2"><b>Type: </b>{c.type}</Typography>
|
||||
</span>
|
||||
</MythicStyledTableCell>
|
||||
<MythicTableCell>
|
||||
<Typography variant="body2" style={{whiteSpace: "pre-line",wordBreak: "break-all",}}>{c.comment}</Typography>
|
||||
<MythicTableCell className="mythic-dashboard-table-cell-wrap">
|
||||
<Typography className="mythic-dashboard-table-comment" variant="body2">{c.comment}</Typography>
|
||||
</MythicTableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
@@ -1782,6 +1850,98 @@ const DashboardElementOptions = [
|
||||
"Credentials"
|
||||
].sort();
|
||||
|
||||
const DashboardElementDetails = {
|
||||
"Active Callbacks": {category: "Metric", summary: "Active, total, and recent callback check-ins."},
|
||||
"Activity Timeline": {category: "Trend", summary: "Task and callback activity over time."},
|
||||
"Credentials": {category: "Table", summary: "Recently captured credentials."},
|
||||
"Downloads": {category: "Table", summary: "Recently downloaded files."},
|
||||
"Eventing Status": {category: "Table", summary: "Recent eventing workflow executions."},
|
||||
"Health - Installed Services": {category: "Metric", summary: "Installed service health."},
|
||||
"My Operations": {category: "Table", summary: "Operations, operators, and current status."},
|
||||
"Operator Activity": {category: "Chart", summary: "Task volume by operator."},
|
||||
"Payloads": {category: "Table", summary: "Recently created payloads."},
|
||||
"Proxy Usage": {category: "Table", summary: "Top proxy traffic."},
|
||||
"Screenshots": {category: "Media", summary: "Recent screenshots preview."},
|
||||
"Task Completion Status": {category: "Chart", summary: "Task success, error, and processing states."},
|
||||
"Tasking": {category: "Table", summary: "Recently issued tasks."},
|
||||
"Top 10 Active Hosts": {category: "Table", summary: "Most active hosts."},
|
||||
"Top 10 Failed Commands": {category: "Chart", summary: "Commands with the most errors."},
|
||||
"Top 10 Host Contexts": {category: "Table", summary: "Hosts with the most tasking."},
|
||||
"Top 10 Issued Commands": {category: "Chart", summary: "Most frequently issued commands."},
|
||||
"Top 10 TagTypes": {category: "Chart", summary: "Most used tag types."},
|
||||
"Top 10 Task Artifacts": {category: "Chart", summary: "Most common task artifacts."},
|
||||
"Top 10 User Contexts": {category: "Table", summary: "User contexts with the most tasking."},
|
||||
};
|
||||
|
||||
const DashboardWidgetSelectDialog = ({onClose, onSubmit, rowIndex}) => {
|
||||
const [selected, setSelected] = React.useState(DashboardElementOptions[0] || "");
|
||||
const submitSelection = (option = selected) => {
|
||||
if(option === ""){
|
||||
return;
|
||||
}
|
||||
onSubmit({id: option});
|
||||
onClose();
|
||||
};
|
||||
const handleKeyDown = (event, option) => {
|
||||
if(event.key === "Enter"){
|
||||
event.preventDefault();
|
||||
submitSelection(option);
|
||||
} else if(event.key === " "){
|
||||
event.preventDefault();
|
||||
setSelected(option);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<DialogTitle>Add dashboard widget</DialogTitle>
|
||||
<DialogContent dividers={true} className="mythic-dashboard-widget-dialog">
|
||||
<div className="mythic-dashboard-widget-dialog-header">
|
||||
<div>
|
||||
<div className="mythic-dashboard-widget-dialog-title">Row {rowIndex + 1}</div>
|
||||
<div className="mythic-dashboard-widget-dialog-subtitle">{selected || "No widget selected"}</div>
|
||||
</div>
|
||||
{selected !== "" &&
|
||||
<span className="mythic-dashboard-widget-category">
|
||||
{DashboardElementDetails[selected]?.category || "Widget"}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div className="mythic-dashboard-widget-grid">
|
||||
{DashboardElementOptions.map((option) => {
|
||||
const details = DashboardElementDetails[option] || {category: "Widget", summary: "Dashboard widget."};
|
||||
return (
|
||||
<div
|
||||
aria-pressed={selected === option}
|
||||
className={`mythic-dashboard-widget-option ${selected === option ? "mythic-dashboard-widget-option-selected" : ""}`}
|
||||
key={"dashboard-widget-option-" + option}
|
||||
onClick={() => setSelected(option)}
|
||||
onDoubleClick={() => submitSelection(option)}
|
||||
onKeyDown={(event) => handleKeyDown(event, option)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className="mythic-dashboard-widget-option-top">
|
||||
<span className="mythic-dashboard-widget-option-title">{option}</span>
|
||||
<span className="mythic-dashboard-widget-category">{details.category}</span>
|
||||
</div>
|
||||
<div className="mythic-dashboard-widget-option-summary">{details.summary}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions className="mythic-dialog-actions">
|
||||
<Button onClick={onClose} variant="outlined">
|
||||
Close
|
||||
</Button>
|
||||
<Button className="mythic-dialog-button-primary" disabled={selected === ""} onClick={() => submitSelection()} variant="contained">
|
||||
Add widget
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomDashboard = ({me, setLoading, loading, editing}) => {
|
||||
const initialDashboardView = GetMythicSetting({
|
||||
setting_name: "customDashboardElements",
|
||||
@@ -1919,24 +2079,22 @@ const CustomDashboard = ({me, setLoading, loading, editing}) => {
|
||||
<div className="mythic-dashboard-custom">
|
||||
{dashboards.map((d, i) => (
|
||||
<div key={"dashboardRow" + i}
|
||||
className="mythic-dashboard-row">
|
||||
className={`mythic-dashboard-row ${editing ? "mythic-dashboard-row-editing" : ""}`.trim()}
|
||||
style={{"--mythic-dashboard-row-template": getDashboardRowTemplate(d)}}>
|
||||
{editing &&
|
||||
<div className="mythic-dashboard-edit-rail">
|
||||
<MythicStyledTooltip title={"Add New Chart To This Row"}>
|
||||
<IconButton className="mythic-dashboard-icon-button" onClick={() => setOpenAddElement({open: true, row: i})} size="small">
|
||||
<AddchartIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Add Row After This Row"}>
|
||||
<IconButton className="mythic-dashboard-icon-button" onClick={() => addDashboardRow(i)} size="small">
|
||||
<FormatListBulletedAddIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Remove This Row"}>
|
||||
<IconButton className="mythic-dashboard-icon-button mythic-dashboard-icon-button-hover-danger" onClick={() => removeDashboardRow(i)} size="small">
|
||||
<PlaylistRemoveIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<div className="mythic-dashboard-edit-toolbar">
|
||||
<span className="mythic-dashboard-edit-toolbar-title">Row {i + 1}</span>
|
||||
<div className="mythic-dashboard-edit-toolbar-actions">
|
||||
<Button className="mythic-dashboard-table-action mythic-table-row-action-hover-info" onClick={() => setOpenAddElement({open: true, row: i})} size="small" startIcon={<AddchartIcon fontSize="small" />}>
|
||||
Add widget
|
||||
</Button>
|
||||
<Button className="mythic-dashboard-table-action" onClick={() => addDashboardRow(i)} size="small" startIcon={<FormatListBulletedAddIcon fontSize="small" />}>
|
||||
Add row
|
||||
</Button>
|
||||
<Button className="mythic-dashboard-table-action mythic-dashboard-table-action-hover-danger" onClick={() => removeDashboardRow(i)} size="small" startIcon={<PlaylistRemoveIcon fontSize="small" />}>
|
||||
Remove row
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{d.map((e,j) => (
|
||||
@@ -1948,14 +2106,11 @@ const CustomDashboard = ({me, setLoading, loading, editing}) => {
|
||||
{openAddElement.open &&
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openAddElement.open}
|
||||
onClose={()=>{setOpenAddElement({open: false, row: 0});}}
|
||||
innerDialog={<MythicSelectFromListDialog onClose={()=>{setOpenAddElement({open: false, row: 0})}}
|
||||
onSubmit={addDashboardElement}
|
||||
options={DashboardElementOptions.map(e => {return{id: e}})}
|
||||
title={"Select Dashboard Element to Add"}
|
||||
action={"select"}
|
||||
identifier={"id"}
|
||||
display={"id"}
|
||||
dontCloseOnSubmit={false} />}
|
||||
innerDialog={<DashboardWidgetSelectDialog
|
||||
onClose={()=>{setOpenAddElement({open: false, row: 0})}}
|
||||
onSubmit={addDashboardElement}
|
||||
rowIndex={openAddElement.row}
|
||||
/>}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
@@ -1972,9 +2127,12 @@ export function CallbacksCard({me}) {
|
||||
});
|
||||
const [dashboard, setDashboard] = React.useState(initialDashboardView);
|
||||
const [updateSetting] = useSetMythicSetting();
|
||||
const onChangeDashboardOption = (event) => {
|
||||
setDashboard(event.target.value);
|
||||
updateSetting({setting_name: "dashboard", value: event.target.value});
|
||||
const onChangeDashboardOption = (event, value) => {
|
||||
if(value === null || value === dashboard){
|
||||
return;
|
||||
}
|
||||
setDashboard(value);
|
||||
updateSetting({setting_name: "dashboard", value: value});
|
||||
setLoading(true);
|
||||
}
|
||||
return (
|
||||
@@ -1994,51 +2152,31 @@ export function CallbacksCard({me}) {
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
<TextField
|
||||
sx={{
|
||||
width: "11rem",
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.08)",
|
||||
color: "inherit",
|
||||
minHeight: 30,
|
||||
},
|
||||
"& .MuiInputLabel-root": {
|
||||
color: "currentColor",
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: "rgba(255, 255, 255, 0.24)",
|
||||
},
|
||||
"& .MuiSelect-select": {
|
||||
py: "4px",
|
||||
},
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: "inherit",
|
||||
},
|
||||
}}
|
||||
size={"small"}
|
||||
select={true}
|
||||
label={"Dashboard Perspective"}
|
||||
value={dashboard}
|
||||
onChange={onChangeDashboardOption}
|
||||
>
|
||||
{
|
||||
dashboardOptions.map((opt, i) => (
|
||||
<MenuItem key={"searchopt" + opt} value={opt}>{opt}</MenuItem>
|
||||
))
|
||||
}
|
||||
</TextField>
|
||||
<MythicStyledTooltip title={"Analyze Operation Data Again"} tooltipStyle={{display: "inline-block"}}>
|
||||
<IconButton onClick={() => setLoading(true)}>
|
||||
<ReplayIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
{dashboard === "custom" &&
|
||||
<MythicStyledTooltip title={editing ? "Stop Editing Dashboard Contents":"Edit Dashboard Contents"}>
|
||||
<IconButton onClick={() => setEditing(!editing)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<ToggleButtonGroup
|
||||
className="mythic-dashboard-perspective-toggle"
|
||||
exclusive
|
||||
onChange={onChangeDashboardOption}
|
||||
size="small"
|
||||
value={dashboard}
|
||||
>
|
||||
{dashboardOptions.map((opt) => (
|
||||
<ToggleButton key={"dashboard-perspective-" + opt} value={opt}>
|
||||
{opt}
|
||||
</ToggleButton>
|
||||
))}
|
||||
</ToggleButtonGroup>
|
||||
<MythicStyledTooltip title={"Analyze Operation Data Again"} tooltipStyle={{display: "inline-block"}}>
|
||||
<Button className="mythic-table-row-action-hover-info" onClick={() => setLoading(true)} size="small" startIcon={<ReplayIcon fontSize="small" />} variant="outlined">
|
||||
Refresh
|
||||
</Button>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
{dashboard === "custom" &&
|
||||
<MythicStyledTooltip title={editing ? "Stop Editing Dashboard Contents":"Edit Dashboard Contents"}>
|
||||
<Button className={editing ? "mythic-table-row-action-hover-success" : "mythic-table-row-action-hover-info"} onClick={() => setEditing(!editing)} size="small" startIcon={<EditIcon fontSize="small" />} variant="outlined">
|
||||
{editing ? "Done editing" : "Edit layout"}
|
||||
</Button>
|
||||
</MythicStyledTooltip>
|
||||
}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -46,12 +46,13 @@ const DashboardCard = ({
|
||||
className = "",
|
||||
editing,
|
||||
removeElement,
|
||||
size = "standard",
|
||||
title,
|
||||
width = "100%",
|
||||
}) => {
|
||||
return (
|
||||
<Paper
|
||||
className={`mythic-dashboard-card ${className}`.trim()}
|
||||
className={`mythic-dashboard-card mythic-dashboard-card-${size} ${className}`.trim()}
|
||||
elevation={0}
|
||||
style={{width}}
|
||||
>
|
||||
@@ -107,25 +108,27 @@ export const DashboardEmptyCard = ({action, children, editing, removeElement, ti
|
||||
);
|
||||
|
||||
const DashboardNoDataState = ({
|
||||
action,
|
||||
title = "No data yet",
|
||||
description = "This dashboard element will populate when matching operation activity exists.",
|
||||
}) => (
|
||||
<MythicEmptyState
|
||||
action={action}
|
||||
compact
|
||||
title={title}
|
||||
description={description}
|
||||
minHeight={132}
|
||||
sx={{p: 0}}
|
||||
minHeight={0}
|
||||
sx={{flex: "1 1 auto", height: "100%", minHeight: 0, p: 0}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const PieChartCard = ({
|
||||
data, width = "100%", additionalStyles, innerElement,
|
||||
margin = {
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
left: 8,
|
||||
right: 8,
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
}, colors,
|
||||
onClick, title = "", editing, removeElement, customizeElement
|
||||
}) => {
|
||||
@@ -142,75 +145,85 @@ export const PieChartCard = ({
|
||||
<>
|
||||
{customizeElement}
|
||||
<MythicStyledTooltip title={showLegend ? "Hide Legend" : "Show Legend"}>
|
||||
<IconButton className="mythic-dashboard-icon-button" onClick={toggleLegend} size="small">
|
||||
<IconButton className="mythic-dashboard-icon-button mythic-dashboard-icon-button-hover-info" onClick={toggleLegend} size="small">
|
||||
{showLegend ? <VisibilityIcon fontSize="small" /> : <VisibilityOffIcon fontSize="small" />}
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</>
|
||||
}
|
||||
bodyClassName="mythic-dashboard-chart-body"
|
||||
editing={editing}
|
||||
removeElement={removeElement}
|
||||
title={title}
|
||||
width={width}
|
||||
>
|
||||
{hasChartData ? (
|
||||
<PieChart
|
||||
skipAnimation={true}
|
||||
series={[
|
||||
{
|
||||
// item has id, label, value, data
|
||||
//arcLabel: (item) => `${item.label}`,
|
||||
//arcLabelMinAngle: 35,
|
||||
//arcLabelRadius: "60%",
|
||||
data: chartData,
|
||||
highlightScope: {fade: 'global', highlighted: 'item'},
|
||||
faded: {innerRadius: 0, additionalRadius: -10, color: 'gray'},
|
||||
paddingAngle: 1,
|
||||
cornerRadius: 4,
|
||||
innerRadius: 0,
|
||||
...additionalStyles
|
||||
},
|
||||
]}
|
||||
height={200}
|
||||
margin={margin}
|
||||
sx={{
|
||||
[`& .${pieArcLabelClasses.root}`]: {
|
||||
fill: 'white',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
}}
|
||||
colors={colors || getDashboardColors(theme)}
|
||||
onItemClick={onClick}
|
||||
hideLegend={!showLegend}
|
||||
slotProps={{
|
||||
legend: {
|
||||
direction: "vertical", // "horizontal"
|
||||
sx: {
|
||||
gap: "4px", // itemGap (distance between legend items)
|
||||
// CSS class
|
||||
['.MuiChartsLegend-series']: {
|
||||
gap: '8px', // markGap (distance between legend dot and text)
|
||||
},
|
||||
[`.MuiChartsLegend-mark`]: {
|
||||
height: 12, // size of the legend dot
|
||||
width: 12,
|
||||
},
|
||||
<div className="mythic-dashboard-chart-canvas mythic-dashboard-chart-canvas-pie">
|
||||
<PieChart
|
||||
skipAnimation={true}
|
||||
series={[
|
||||
{
|
||||
data: chartData,
|
||||
highlightScope: {fade: 'global', highlighted: 'item'},
|
||||
faded: {innerRadius: 0, additionalRadius: -10, color: 'gray'},
|
||||
paddingAngle: 1,
|
||||
cornerRadius: 4,
|
||||
innerRadius: 0,
|
||||
...additionalStyles
|
||||
},
|
||||
]}
|
||||
height={210}
|
||||
margin={margin}
|
||||
sx={{
|
||||
[`& .${pieArcLabelClasses.root}`]: {
|
||||
fill: 'white',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
}}
|
||||
colors={colors || getDashboardColors(theme)}
|
||||
onItemClick={onClick}
|
||||
hideLegend={!showLegend}
|
||||
slotProps={{
|
||||
legend: {
|
||||
direction: "vertical",
|
||||
sx: {
|
||||
gap: "5px",
|
||||
['.MuiChartsLegend-series']: {
|
||||
gap: '8px',
|
||||
},
|
||||
[`.MuiChartsLegend-mark`]: {
|
||||
height: 10,
|
||||
rx: 2,
|
||||
width: 10,
|
||||
},
|
||||
[`.MuiChartsLegend-label`]: {
|
||||
fontSize: 11,
|
||||
fontWeight: 650,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}}>
|
||||
{innerElement}
|
||||
</PieChart>
|
||||
}}>
|
||||
{innerElement}
|
||||
</PieChart>
|
||||
</div>
|
||||
) : (
|
||||
<DashboardNoDataState />
|
||||
<div className="mythic-dashboard-chart-canvas mythic-dashboard-chart-canvas-empty">
|
||||
<DashboardNoDataState />
|
||||
</div>
|
||||
)}
|
||||
</DashboardCard>
|
||||
);
|
||||
}
|
||||
export const GaugeCard = ({data, width = "100%", title = "", editing, removeElement, customizeElement }) => {
|
||||
const theme = useTheme();
|
||||
const online = data?.online || 0;
|
||||
const total = data?.total || 0;
|
||||
const percentOnline = total > 0 ? Math.round((online / total) * 100) : 0;
|
||||
const statusLevel = total === 0 ? "neutral" : percentOnline > 85 ? "success" : percentOnline > 50 ? "warning" : "danger";
|
||||
const statusLabel = total === 0 ? "No services" : percentOnline > 85 ? "Healthy" : percentOnline > 50 ? "Degraded" : "Attention";
|
||||
const getFillColor = () => {
|
||||
if(data['total'] === 0){return theme.palette.text.disabled}
|
||||
let ratio = data['online'] / data['total'];
|
||||
if(total === 0){return theme.palette.text.disabled}
|
||||
let ratio = online / total;
|
||||
if( ratio > 0.85){
|
||||
return theme.palette.success.main;
|
||||
}else if(ratio > 0.5){
|
||||
@@ -222,55 +235,105 @@ export const GaugeCard = ({data, width = "100%", title = "", editing, removeElem
|
||||
return (
|
||||
<DashboardCard
|
||||
actions={customizeElement}
|
||||
bodyClassName="mythic-dashboard-card-body-centered"
|
||||
bodyClassName="mythic-dashboard-kpi-body"
|
||||
editing={editing}
|
||||
removeElement={removeElement}
|
||||
size="metric"
|
||||
title={title}
|
||||
width={width}
|
||||
>
|
||||
<Gauge
|
||||
height={200}
|
||||
width={200}
|
||||
skipAnimation={true}
|
||||
valueMax={data['total'] > 0 ? data['total'] : 100}
|
||||
value={data['online']}
|
||||
innerRadius={"70%"}
|
||||
cornerRadius="20%"
|
||||
text={({ value, valueMax }) => `${value} / ${valueMax}`}
|
||||
sx={() => ({
|
||||
[`& .MuiGauge-valueText > text > tspan`]: {
|
||||
fontSize: 30,
|
||||
},
|
||||
[`.MuiGauge-valueArc`]: {
|
||||
fill: getFillColor(),
|
||||
},
|
||||
})}
|
||||
>
|
||||
</Gauge>
|
||||
<div className="mythic-dashboard-service-kpi">
|
||||
<div className="mythic-dashboard-kpi-main">
|
||||
<div className="mythic-dashboard-kpi-status-row">
|
||||
<span className={`mythic-dashboard-kpi-chip mythic-dashboard-kpi-chip-${statusLevel}`}>
|
||||
{statusLabel}
|
||||
</span>
|
||||
<span className="mythic-dashboard-kpi-percent">{percentOnline}%</span>
|
||||
</div>
|
||||
<div className="mythic-dashboard-kpi-value-row">
|
||||
<span className="mythic-dashboard-kpi-value">{online}</span>
|
||||
<span className="mythic-dashboard-kpi-total">/ {total}</span>
|
||||
</div>
|
||||
<div className="mythic-dashboard-kpi-label">
|
||||
Services online
|
||||
</div>
|
||||
</div>
|
||||
<div className="mythic-dashboard-kpi-gauge">
|
||||
<Gauge
|
||||
height={112}
|
||||
width={112}
|
||||
skipAnimation={true}
|
||||
valueMax={total > 0 ? total : 100}
|
||||
value={online}
|
||||
innerRadius={"72%"}
|
||||
cornerRadius="20%"
|
||||
text={() => `${percentOnline}%`}
|
||||
sx={() => ({
|
||||
[`& .MuiGauge-valueText > text > tspan`]: {
|
||||
fontSize: 18,
|
||||
fontWeight: 850,
|
||||
},
|
||||
[`.MuiGauge-valueArc`]: {
|
||||
fill: getFillColor(),
|
||||
},
|
||||
})}
|
||||
>
|
||||
</Gauge>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardCard>
|
||||
);
|
||||
}
|
||||
export const CallbackDataCard = ({mainTitle, secondTitle, mainElement, secondaryElement, width="100%",
|
||||
editing, removeElement}) => {
|
||||
export const CallbackDataCard = ({mainTitle, primaryValue, totalValue, primaryLabel, secondaryValue, secondaryLabel,
|
||||
statusLabel = "Tracking", statusLevel = "info", onClick, width="100%",
|
||||
actions, editing, removeElement}) => {
|
||||
const handleKeyDown = (event) => {
|
||||
if(!onClick){
|
||||
return;
|
||||
}
|
||||
if(event.key === "Enter" || event.key === " "){
|
||||
event.preventDefault();
|
||||
onClick();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<DashboardCard
|
||||
bodyClassName="mythic-dashboard-metric-body"
|
||||
actions={actions}
|
||||
bodyClassName="mythic-dashboard-kpi-body"
|
||||
editing={editing}
|
||||
removeElement={removeElement}
|
||||
size="metric"
|
||||
title={mainTitle}
|
||||
width={width}
|
||||
>
|
||||
<div className="mythic-dashboard-metric-content">
|
||||
<MythicStyledTooltip title={"Go to Active Callbacks"}>
|
||||
<div className="mythic-dashboard-metric-link">
|
||||
{mainElement}
|
||||
<div className="mythic-dashboard-metric-label">
|
||||
{secondTitle}
|
||||
</div>
|
||||
{secondaryElement}
|
||||
<MythicStyledTooltip title={"Go to Active Callbacks"}>
|
||||
<div
|
||||
className="mythic-dashboard-callback-kpi"
|
||||
onClick={onClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
role={onClick ? "button" : undefined}
|
||||
tabIndex={onClick ? 0 : undefined}
|
||||
>
|
||||
<div className="mythic-dashboard-kpi-main">
|
||||
<div className="mythic-dashboard-kpi-status-row">
|
||||
<span className={`mythic-dashboard-kpi-chip mythic-dashboard-kpi-chip-${statusLevel}`}>
|
||||
{statusLabel}
|
||||
</span>
|
||||
</div>
|
||||
</MythicStyledTooltip>
|
||||
<div className="mythic-dashboard-kpi-value-row">
|
||||
<span className="mythic-dashboard-kpi-value">{primaryValue}</span>
|
||||
<span className="mythic-dashboard-kpi-total">/ {totalValue}</span>
|
||||
</div>
|
||||
<div className="mythic-dashboard-kpi-label">
|
||||
{primaryLabel}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mythic-dashboard-kpi-secondary-panel">
|
||||
<span className="mythic-dashboard-kpi-secondary-value">{secondaryValue}</span>
|
||||
<span className="mythic-dashboard-kpi-secondary-label">{secondaryLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
</MythicStyledTooltip>
|
||||
</DashboardCard>
|
||||
)
|
||||
}
|
||||
@@ -285,21 +348,32 @@ export const TableDataCard = ({
|
||||
empty = false,
|
||||
emptyTitle,
|
||||
emptyDescription,
|
||||
emptyAction,
|
||||
summary = true,
|
||||
tableClassName = "",
|
||||
}) => {
|
||||
const tableClasses = [
|
||||
"mythic-dashboard-table",
|
||||
summary ? "mythic-dashboard-summary-table" : "",
|
||||
tableClassName,
|
||||
].filter(Boolean).join(" ");
|
||||
return (
|
||||
<DashboardCard
|
||||
actions={customizeElement}
|
||||
bodyClassName="mythic-dashboard-table-body"
|
||||
editing={editing}
|
||||
removeElement={removeElement}
|
||||
size="table"
|
||||
title={title}
|
||||
width={width}
|
||||
>
|
||||
{empty ? (
|
||||
<DashboardNoDataState title={emptyTitle} description={emptyDescription} />
|
||||
<TableContainer className="mythic-dashboard-table-container mythic-dashboard-empty-container mythicElement">
|
||||
<DashboardNoDataState title={emptyTitle} description={emptyDescription} action={emptyAction} />
|
||||
</TableContainer>
|
||||
) : (
|
||||
<TableContainer className="mythic-dashboard-table-container mythicElement">
|
||||
<Table className="mythic-dashboard-table" stickyHeader size="small">
|
||||
<Table className={tableClasses} stickyHeader size="small">
|
||||
{tableHead}
|
||||
{tableBody}
|
||||
</Table>
|
||||
@@ -338,57 +412,57 @@ export const LineTimeChartCard = ({data, additionalStyles}) => {
|
||||
}
|
||||
};
|
||||
return (
|
||||
<DashboardCard title="Tasks Issued per Day">
|
||||
<LineChart
|
||||
xAxis={[
|
||||
{
|
||||
dataKey: 'x',
|
||||
//valueFormatter: (v) => (new Date(v)).toISOString().substr(0, 10),
|
||||
scaleType: "time",
|
||||
min: data[value[0]]?.x || 0,
|
||||
max: data[value[1]]?.x || 0,
|
||||
id: 'bottomAxis',
|
||||
labelStyle: {
|
||||
fontSize: 10,
|
||||
},
|
||||
tickLabelStyle: {
|
||||
angle: 25,
|
||||
textAnchor: 'start',
|
||||
fontSize: 5,
|
||||
},
|
||||
<DashboardCard bodyClassName="mythic-dashboard-chart-body" title="Tasks Issued per Day" size="wide">
|
||||
<div className="mythic-dashboard-chart-canvas mythic-dashboard-chart-canvas-line">
|
||||
<LineChart
|
||||
xAxis={[
|
||||
{
|
||||
dataKey: 'x',
|
||||
scaleType: "time",
|
||||
min: data[value[0]]?.x || 0,
|
||||
max: data[value[1]]?.x || 0,
|
||||
id: 'bottomAxis',
|
||||
labelStyle: {
|
||||
fontSize: 10,
|
||||
},
|
||||
tickLabelStyle: {
|
||||
angle: 0,
|
||||
fontSize: 10,
|
||||
},
|
||||
|
||||
},
|
||||
]}
|
||||
series={[
|
||||
{
|
||||
dataKey: 'y',
|
||||
label: "mythic_admin",
|
||||
showMark: ({index}) => index % 2 === 0,
|
||||
//color: "#4e79a7"
|
||||
}
|
||||
]}
|
||||
sx={{
|
||||
[`.${axisClasses.left} .${axisClasses.label}`]: {
|
||||
transform: 'translate(-25px, 0)',
|
||||
},
|
||||
[`.${axisClasses.right} .${axisClasses.label}`]: {
|
||||
transform: 'translate(30px, 0)',
|
||||
},
|
||||
}}
|
||||
margin={{ top: 10 }}
|
||||
dataset={data}
|
||||
height={200}
|
||||
{...additionalStyles}
|
||||
></LineChart>
|
||||
<Slider
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
valueLabelDisplay="auto"
|
||||
min={range[0]}
|
||||
max={range[1]}
|
||||
className="mythic-dashboard-slider"
|
||||
sx={{ width: "80%" }}
|
||||
/>
|
||||
},
|
||||
]}
|
||||
series={[
|
||||
{
|
||||
dataKey: 'y',
|
||||
label: "mythic_admin",
|
||||
showMark: ({index}) => index % 2 === 0,
|
||||
}
|
||||
]}
|
||||
sx={{
|
||||
[`.${axisClasses.left} .${axisClasses.label}`]: {
|
||||
transform: 'translate(-25px, 0)',
|
||||
},
|
||||
[`.${axisClasses.right} .${axisClasses.label}`]: {
|
||||
transform: 'translate(30px, 0)',
|
||||
},
|
||||
}}
|
||||
margin={{ top: 18, right: 20, bottom: 28, left: 42 }}
|
||||
dataset={data}
|
||||
height={186}
|
||||
{...additionalStyles}
|
||||
></LineChart>
|
||||
</div>
|
||||
<div className="mythic-dashboard-chart-slider-row">
|
||||
<Slider
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
valueLabelDisplay="auto"
|
||||
min={range[0]}
|
||||
max={range[1]}
|
||||
className="mythic-dashboard-slider"
|
||||
/>
|
||||
</div>
|
||||
</DashboardCard>
|
||||
|
||||
)
|
||||
@@ -450,73 +524,79 @@ export const LineTimeMultiChartCard = ({data, additionalStyles, colors, view_utc
|
||||
<>
|
||||
{customizeElement}
|
||||
<MythicStyledTooltip title={showLegend ? "Hide Legend" : "Show Legend"}>
|
||||
<IconButton className="mythic-dashboard-icon-button" onClick={toggleLegend} size="small">
|
||||
<IconButton className="mythic-dashboard-icon-button mythic-dashboard-icon-button-hover-info" onClick={toggleLegend} size="small">
|
||||
{showLegend ? <VisibilityIcon fontSize="small" /> : <VisibilityOffIcon fontSize="small" />}
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
</>
|
||||
}
|
||||
bodyClassName="mythic-dashboard-chart-body"
|
||||
editing={editing}
|
||||
removeElement={removeElement}
|
||||
size="wide"
|
||||
title={`Activity per Day ${view_utc_time ? "(UTC)" : `(${Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone})`}`}
|
||||
>
|
||||
{hasChartData ? (
|
||||
<>
|
||||
<LineChart
|
||||
colors={colors || getDashboardColors(theme)}
|
||||
hideLegend={!showLegend}
|
||||
xAxis={[
|
||||
{
|
||||
data: data.x,
|
||||
scaleType: "time",
|
||||
min: data?.x?.[value[0]] || 0,
|
||||
max: data?.x?.[value[1]] || 0,
|
||||
id: 'bottomAxis',
|
||||
tickMinStep: 86400000,
|
||||
labelStyle: {
|
||||
<div className="mythic-dashboard-chart-canvas mythic-dashboard-chart-canvas-line">
|
||||
<LineChart
|
||||
colors={colors || getDashboardColors(theme)}
|
||||
hideLegend={!showLegend}
|
||||
xAxis={[
|
||||
{
|
||||
data: data.x,
|
||||
scaleType: "time",
|
||||
min: data?.x?.[value[0]] || 0,
|
||||
max: data?.x?.[value[1]] || 0,
|
||||
id: 'bottomAxis',
|
||||
tickMinStep: 86400000,
|
||||
labelStyle: {
|
||||
fontSize: 10,
|
||||
},
|
||||
tickLabelStyle: {
|
||||
angle: 0,
|
||||
fontSize: 10,
|
||||
},
|
||||
},
|
||||
]}
|
||||
yAxis={[
|
||||
{id: "taskAxis", scaleType: "linear", label: "Tasks Issued"},
|
||||
{id: "callbackAxis", scaleType: "linear", label: "Active Callbacks", position: "right"}
|
||||
]}
|
||||
series={data.y}
|
||||
sx={{
|
||||
[`.${axisClasses.left} .${axisClasses.label}`]: {
|
||||
fontSize: 10,
|
||||
},
|
||||
tickLabelStyle: {
|
||||
angle: 25,
|
||||
textAnchor: 'start',
|
||||
fontSize: 5,
|
||||
[`.${axisClasses.right} .${axisClasses.label}`]: {
|
||||
fontSize: 10,
|
||||
},
|
||||
},
|
||||
]}
|
||||
yAxis={[
|
||||
{id: "taskAxis", scaleType: "linear", label: "Tasks Issued"},
|
||||
{id: "callbackAxis", scaleType: "linear", label: "Active Callbacks", position: "right"}
|
||||
]}
|
||||
series={data.y}
|
||||
sx={{
|
||||
[`.${axisClasses.left} .${axisClasses.label}`]: {
|
||||
//transform: 'translate(-25px, 0)',
|
||||
},
|
||||
[`.${axisClasses.right} .${axisClasses.label}`]: {
|
||||
//transform: 'translate(30px, 0)',
|
||||
},
|
||||
}}
|
||||
margin={{ }}
|
||||
height={200}
|
||||
{...additionalStyles}
|
||||
></LineChart>
|
||||
<Slider
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
size={"small"}
|
||||
valueLabelDisplay={"auto"}
|
||||
valueLabelFormat={sliderVal => sliderDate(sliderVal, view_utc_time)}
|
||||
min={range[0]}
|
||||
max={range[1]}
|
||||
className="mythic-dashboard-slider"
|
||||
sx={{ width: "80%" }}
|
||||
/>
|
||||
}}
|
||||
margin={{ top: 18, right: 46, bottom: 30, left: 48 }}
|
||||
height={186}
|
||||
{...additionalStyles}
|
||||
></LineChart>
|
||||
</div>
|
||||
<div className="mythic-dashboard-chart-slider-row">
|
||||
<Slider
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
size={"small"}
|
||||
valueLabelDisplay={"auto"}
|
||||
valueLabelFormat={sliderVal => sliderDate(sliderVal, view_utc_time)}
|
||||
min={range[0]}
|
||||
max={range[1]}
|
||||
className="mythic-dashboard-slider"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<DashboardNoDataState
|
||||
title="No activity yet"
|
||||
description="Task and callback activity will appear here once the operation has timeline data."
|
||||
/>
|
||||
<div className="mythic-dashboard-chart-canvas mythic-dashboard-chart-canvas-empty">
|
||||
<DashboardNoDataState
|
||||
title="No activity yet"
|
||||
description="Task and callback activity will appear here once the operation has timeline data."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</DashboardCard>
|
||||
|
||||
|
||||
@@ -3337,14 +3337,18 @@ tspan {
|
||||
}
|
||||
.mythic-dashboard-row {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
display: grid;
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
grid-auto-flow: dense;
|
||||
grid-template-columns: var(--mythic-dashboard-row-template, repeat(auto-fit, minmax(min(100%, 19rem), 1fr)));
|
||||
min-width: 0;
|
||||
padding: 0 0.75rem;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-dashboard-row-editing {
|
||||
align-items: start;
|
||||
}
|
||||
.mythic-dashboard-card {
|
||||
background-color: ${(props) => props.theme.surfaces?.raised || props.theme.palette.background.paper};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
@@ -3353,23 +3357,59 @@ tspan {
|
||||
display: flex;
|
||||
flex: 1 1 18rem;
|
||||
flex-direction: column;
|
||||
grid-column: span 1;
|
||||
height: var(--mythic-dashboard-card-height, 18rem);
|
||||
max-height: var(--mythic-dashboard-card-height, 18rem);
|
||||
min-height: var(--mythic-dashboard-card-height, 18rem);
|
||||
min-width: min(100%, 18rem);
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-dashboard-card:hover {
|
||||
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.34 : 0.24)};
|
||||
box-shadow: ${(props) => props.theme.palette.mode === "dark" ? `inset 0 1px 0 ${alpha(props.theme.palette.common.white, 0.055)}, 0 8px 18px ${alpha(props.theme.palette.common.black, 0.18)}` : `0 8px 18px ${alpha(props.theme.palette.common.black, 0.08)}`} !important;
|
||||
}
|
||||
.mythic-dashboard-card-wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
.mythic-dashboard-card-table {
|
||||
grid-column: span 2;
|
||||
max-width: min(100%, 58rem);
|
||||
}
|
||||
.mythic-dashboard-row > .mythic-dashboard-card-table:only-child {
|
||||
max-width: min(100%, 64rem);
|
||||
}
|
||||
.mythic-dashboard-card-metric {
|
||||
grid-column: span 1;
|
||||
}
|
||||
.mythic-dashboard-card-header {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.default};
|
||||
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
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.16 : 0.09)} 0%, ${alpha(headerTextColor, props.theme.palette.mode === "dark" ? 0.05 : 0.035)} 100%)`;
|
||||
}};
|
||||
border-bottom: 1px solid ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.34 : 0.2)};
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
gap: 0.65rem;
|
||||
justify-content: space-between;
|
||||
min-height: 42px;
|
||||
min-width: 0;
|
||||
padding: 0.55rem 0.65rem;
|
||||
overflow: hidden;
|
||||
padding: 0.55rem 0.65rem 0.55rem 0.85rem;
|
||||
position: relative;
|
||||
}
|
||||
.mythic-dashboard-card-header::before {
|
||||
background-color: ${(props) => props.theme.palette.primary.main};
|
||||
bottom: 0;
|
||||
content: "";
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 4px;
|
||||
}
|
||||
.mythic-dashboard-card-title {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
@@ -3423,11 +3463,23 @@ tspan {
|
||||
.mythic-dashboard-icon-button-hover-danger:hover {
|
||||
color: ${(props) => props.theme.palette.error.main} !important;
|
||||
}
|
||||
.mythic-dashboard-icon-button-hover-info:hover {
|
||||
background-color: ${(props) => props.theme.palette.info.main + "2b"} !important;
|
||||
border-color: ${(props) => props.theme.palette.info.main + "88"} !important;
|
||||
color: ${(props) => props.theme.palette.info.main} !important;
|
||||
}
|
||||
.mythic-dashboard-icon-button.Mui-disabled {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)"} !important;
|
||||
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
color: ${(props) => props.theme.palette.text.disabled} !important;
|
||||
}
|
||||
.mythic-dashboard-perspective-toggle {
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-perspective-toggle .MuiToggleButton-root {
|
||||
min-width: 5.6rem !important;
|
||||
}
|
||||
.mythic-dashboard-primary-button,
|
||||
.mythic-dashboard-table-action {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)"} !important;
|
||||
@@ -3452,34 +3504,244 @@ tspan {
|
||||
.mythic-dashboard-table-action:hover {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.06)"} !important;
|
||||
}
|
||||
.mythic-dashboard-table-action-info {
|
||||
background-color: ${(props) => props.theme.palette.info.main + "1c"} !important;
|
||||
border-color: ${(props) => props.theme.palette.info.main + "66"} !important;
|
||||
color: ${(props) => props.theme.palette.info.main} !important;
|
||||
}
|
||||
.mythic-dashboard-table-action-info:hover {
|
||||
background-color: ${(props) => props.theme.palette.info.main + "2b"} !important;
|
||||
border-color: ${(props) => props.theme.palette.info.main + "88"} !important;
|
||||
.mythic-dashboard-table-action-hover-danger:hover {
|
||||
background-color: ${(props) => props.theme.palette.error.main + "2b"} !important;
|
||||
border-color: ${(props) => props.theme.palette.error.main + "88"} !important;
|
||||
color: ${(props) => props.theme.palette.error.main} !important;
|
||||
}
|
||||
.mythic-dashboard-table-action.Mui-disabled {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)"} !important;
|
||||
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
|
||||
color: ${(props) => props.theme.palette.text.disabled} !important;
|
||||
}
|
||||
.mythic-dashboard-chart-body {
|
||||
gap: 0.35rem;
|
||||
padding: 0.5rem 0.55rem 0.55rem;
|
||||
}
|
||||
.mythic-dashboard-chart-canvas {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.028) : alpha(props.theme.palette.common.black, 0.014)};
|
||||
background-image: ${(props) => `linear-gradient(135deg, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.1 : 0.055)} 0%, ${alpha(props.theme.palette.background.paper, 0)} 62%)`};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-dashboard-chart-canvas-pie {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
.mythic-dashboard-chart-canvas-line {
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
padding: 0.2rem 0.3rem 0;
|
||||
}
|
||||
.mythic-dashboard-chart-canvas-empty {
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.mythic-dashboard-chart-canvas .MuiChartsLegend-root {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
}
|
||||
.mythic-dashboard-chart-canvas .MuiChartsAxis-line,
|
||||
.mythic-dashboard-chart-canvas .MuiChartsAxis-tick {
|
||||
stroke: ${(props) => props.theme.table?.border || props.theme.borderColor};
|
||||
}
|
||||
.mythic-dashboard-chart-canvas .MuiChartsAxis-tickLabel,
|
||||
.mythic-dashboard-chart-canvas .MuiChartsAxis-label {
|
||||
fill: ${(props) => props.theme.palette.text.secondary} !important;
|
||||
}
|
||||
.mythic-dashboard-chart-slider-row {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.03) : alpha(props.theme.palette.common.black, 0.018)};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
justify-content: center;
|
||||
min-height: 2rem;
|
||||
padding: 0.15rem 0.8rem;
|
||||
}
|
||||
.mythic-dashboard-table-body {
|
||||
padding: 0;
|
||||
}
|
||||
.mythic-dashboard-empty-container {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-dashboard-table-container {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.018) : alpha(props.theme.palette.common.black, 0.01)};
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: auto;
|
||||
scrollbar-gutter: stable;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-dashboard-table {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-dashboard-summary-table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
.mythic-dashboard-card-table .MuiTableCell-root {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.mythic-dashboard-summary-table .MuiTableCell-root {
|
||||
font-size: 0.75rem;
|
||||
height: 2.15rem;
|
||||
line-height: 1.25;
|
||||
overflow: hidden;
|
||||
padding-bottom: 0.35rem !important;
|
||||
padding-top: 0.35rem !important;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-dashboard-summary-table .MuiTableRow-hover:hover .MuiTableCell-root {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.035) : alpha(props.theme.palette.common.black, 0.025)};
|
||||
}
|
||||
.mythic-dashboard-table-cell-primary {
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-table-cell-tight,
|
||||
.mythic-dashboard-table-cell-count,
|
||||
.mythic-dashboard-table-cell-actions {
|
||||
text-align: right;
|
||||
white-space: nowrap !important;
|
||||
width: 1%;
|
||||
}
|
||||
.mythic-dashboard-table-cell-count {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-weight: 850;
|
||||
}
|
||||
.mythic-dashboard-table-cell-actions {
|
||||
min-width: 6.25rem;
|
||||
}
|
||||
.mythic-dashboard-table-cell-wrap {
|
||||
overflow: visible !important;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: normal !important;
|
||||
}
|
||||
.mythic-dashboard-table-cell-account {
|
||||
align-items: flex-start;
|
||||
display: flex !important;
|
||||
flex-direction: row;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
.mythic-dashboard-table-cell-wrap .MuiTypography-root,
|
||||
.mythic-dashboard-table-comment {
|
||||
overflow-wrap: anywhere;
|
||||
white-space: normal !important;
|
||||
}
|
||||
.mythic-dashboard-table-identity,
|
||||
.mythic-dashboard-table-file-row,
|
||||
.mythic-dashboard-table-actions-inline {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-table-actions-inline {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.mythic-dashboard-table-file-row {
|
||||
max-width: 100%;
|
||||
}
|
||||
.mythic-dashboard-table-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.12rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-table-primary-text,
|
||||
.mythic-dashboard-table-link,
|
||||
.mythic-dashboard-summary-table .MuiTypography-root {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-dashboard-table-primary-text,
|
||||
.mythic-dashboard-table-link {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
}
|
||||
.mythic-dashboard-table-secondary {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.7rem !important;
|
||||
font-weight: 650;
|
||||
line-height: 1.2 !important;
|
||||
}
|
||||
.mythic-dashboard-table-cell-wrap .MuiTypography-root,
|
||||
.mythic-dashboard-table-cell-wrap .mythic-dashboard-table-primary-text,
|
||||
.mythic-dashboard-table-cell-wrap .mythic-dashboard-table-secondary {
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
white-space: normal !important;
|
||||
}
|
||||
.mythic-dashboard-table-icon-action {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
flex: 0 0 auto;
|
||||
height: 1.1rem;
|
||||
width: 1.1rem;
|
||||
}
|
||||
.mythic-dashboard-table-icon-action:hover,
|
||||
.mythic-dashboard-table-icon-action-info:hover {
|
||||
color: ${(props) => props.theme.palette.info.main};
|
||||
}
|
||||
.mythic-dashboard-tasking-table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
.mythic-dashboard-tasking-cell {
|
||||
max-width: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-dashboard-tasking-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-dashboard-tasking-list .TaskDisplay-root,
|
||||
.mythic-dashboard-tasking-list .TaskDisplayAccordion-root,
|
||||
.mythic-dashboard-tasking-list .TaskDisplayAccordion-content,
|
||||
.mythic-dashboard-tasking-list .TaskDisplayAccordionDetails-root {
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-tasking-list .MuiAccordion-root,
|
||||
.mythic-dashboard-tasking-list .MuiGrid-root,
|
||||
.mythic-dashboard-tasking-list .mythic-response-table,
|
||||
.mythic-dashboard-tasking-list .mythic-response-table-grid {
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-dashboard-tasking-list .MuiCollapse-root,
|
||||
.mythic-dashboard-tasking-list .MuiCollapse-wrapper,
|
||||
.mythic-dashboard-tasking-list .MuiCollapse-wrapperInner {
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mythic-dashboard-card-table .MuiButton-root {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-dashboard-screenshot-row {
|
||||
align-items: center;
|
||||
@@ -3557,6 +3819,182 @@ tspan {
|
||||
.mythic-dashboard-metric-title-row .MuiFormControl-root {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.mythic-dashboard-kpi-body {
|
||||
padding: 0.55rem 0.65rem 0.65rem;
|
||||
}
|
||||
.mythic-dashboard-widget-settings-popover {
|
||||
background-color: ${(props) => props.theme.surfaces?.raised || props.theme.palette.background.paper};
|
||||
border: 1px solid ${(props) => props.theme.table?.border || props.theme.borderColor};
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
max-width: 17rem;
|
||||
min-width: 14rem;
|
||||
padding: 0.8rem;
|
||||
}
|
||||
.mythic-dashboard-widget-settings-title {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.82rem;
|
||||
font-weight: 850;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.mythic-dashboard-widget-settings-copy {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.72rem;
|
||||
font-weight: 650;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.mythic-dashboard-callback-kpi,
|
||||
.mythic-dashboard-service-kpi {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.035) : alpha(props.theme.palette.common.black, 0.018)};
|
||||
background-image: ${(props) => `linear-gradient(135deg, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.13 : 0.075)} 0%, ${alpha(props.theme.palette.background.paper, 0)} 58%)`};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
gap: 0.75rem;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 0.8rem;
|
||||
}
|
||||
.mythic-dashboard-callback-kpi {
|
||||
cursor: pointer;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.mythic-dashboard-callback-kpi:hover,
|
||||
.mythic-dashboard-callback-kpi:focus-visible {
|
||||
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.52 : 0.34)};
|
||||
outline: none;
|
||||
}
|
||||
.mythic-dashboard-service-kpi {
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.mythic-dashboard-kpi-main {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-kpi-status-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.45rem;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-kpi-chip {
|
||||
align-items: center;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.66rem;
|
||||
font-weight: 850;
|
||||
line-height: 1;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0.25rem 0.45rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-dashboard-kpi-chip-info {
|
||||
background-color: ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.22 : 0.12)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.52 : 0.34)};
|
||||
color: ${(props) => props.theme.palette.info.main};
|
||||
}
|
||||
.mythic-dashboard-kpi-chip-success {
|
||||
background-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.22 : 0.12)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.success.main, props.theme.palette.mode === "dark" ? 0.52 : 0.34)};
|
||||
color: ${(props) => props.theme.palette.success.main};
|
||||
}
|
||||
.mythic-dashboard-kpi-chip-warning {
|
||||
background-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.24 : 0.14)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.52 : 0.34)};
|
||||
color: ${(props) => props.theme.palette.warning.main};
|
||||
}
|
||||
.mythic-dashboard-kpi-chip-danger {
|
||||
background-color: ${(props) => alpha(props.theme.palette.error.main, props.theme.palette.mode === "dark" ? 0.22 : 0.12)};
|
||||
border-color: ${(props) => alpha(props.theme.palette.error.main, props.theme.palette.mode === "dark" ? 0.52 : 0.34)};
|
||||
color: ${(props) => props.theme.palette.error.main};
|
||||
}
|
||||
.mythic-dashboard-kpi-chip-neutral {
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.06) : alpha(props.theme.palette.common.black, 0.035)};
|
||||
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
}
|
||||
.mythic-dashboard-kpi-percent {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.74rem;
|
||||
font-weight: 850;
|
||||
line-height: 1;
|
||||
}
|
||||
.mythic-dashboard-kpi-value-row {
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-kpi-value {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 3.35rem;
|
||||
font-weight: 880;
|
||||
letter-spacing: 0;
|
||||
line-height: 0.98;
|
||||
}
|
||||
.mythic-dashboard-kpi-total {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 1.25rem;
|
||||
font-weight: 760;
|
||||
line-height: 1;
|
||||
}
|
||||
.mythic-dashboard-kpi-label {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.78rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.25;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
.mythic-dashboard-kpi-secondary-panel {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.045) : alpha(props.theme.palette.common.black, 0.025)};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
padding: 0.55rem 0.65rem;
|
||||
}
|
||||
.mythic-dashboard-kpi-secondary-value {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
flex: 0 0 auto;
|
||||
font-size: 1.65rem;
|
||||
font-weight: 880;
|
||||
line-height: 1;
|
||||
}
|
||||
.mythic-dashboard-kpi-secondary-label {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-dashboard-kpi-gauge {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 0 7rem;
|
||||
justify-content: center;
|
||||
min-width: 7rem;
|
||||
}
|
||||
.mythic-dashboard-metric-content,
|
||||
.mythic-dashboard-metric-link {
|
||||
display: flex;
|
||||
@@ -3596,7 +4034,15 @@ tspan {
|
||||
}
|
||||
.mythic-dashboard-slider {
|
||||
align-self: center;
|
||||
margin-top: 0.65rem;
|
||||
margin: 0;
|
||||
width: min(100%, 32rem);
|
||||
}
|
||||
.mythic-dashboard-slider .MuiSlider-rail {
|
||||
opacity: 0.28;
|
||||
}
|
||||
.mythic-dashboard-slider .MuiSlider-thumb {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
.mythic-dashboard-edit-rail {
|
||||
align-items: center;
|
||||
@@ -3610,6 +4056,172 @@ tspan {
|
||||
justify-content: center;
|
||||
padding: 0.45rem;
|
||||
}
|
||||
.mythic-dashboard-edit-toolbar {
|
||||
align-items: center;
|
||||
background-color: ${(props) => {
|
||||
const headerSurface = props.theme.pageHeader?.main || props.theme.surfaces?.muted || props.theme.palette.background.paper;
|
||||
return props.theme.palette.mode === "dark" ? headerSurface : alpha(props.theme.palette.primary.main, 0.16);
|
||||
}} !important;
|
||||
background-image: ${(props) => `linear-gradient(90deg, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.44 : 0.32)} 0%, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.22 : 0.22)} 42%, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.08 : 0.12)} 100%)`} !important;
|
||||
border: 1px solid ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.55 : 0.38)};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
box-shadow: inset 0 1px 0 ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.22)}, 0 2px 6px ${(props) => alpha(props.theme.palette.common.black, props.theme.palette.mode === "dark" ? 0.24 : 0.1)};
|
||||
color: ${(props) => props.theme.pageHeaderText?.main || props.theme.palette.text.primary};
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.55rem;
|
||||
grid-column: 1 / -1;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 0.52rem 0.65rem 0.52rem 1rem;
|
||||
position: relative;
|
||||
}
|
||||
.mythic-dashboard-edit-toolbar::before {
|
||||
background-color: ${(props) => props.theme.palette.primary.main};
|
||||
bottom: 0;
|
||||
box-shadow: 0 0 0 1px ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.2)};
|
||||
content: "";
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 6px;
|
||||
}
|
||||
.mythic-dashboard-edit-toolbar-title {
|
||||
color: inherit;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 850;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-dashboard-edit-toolbar-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-edit-toolbar .MuiButton-root.mythic-dashboard-table-action {
|
||||
background-color: ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.1)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.24)} !important;
|
||||
color: ${(props) => props.theme.pageHeaderText?.main || props.theme.palette.text.primary} !important;
|
||||
}
|
||||
.mythic-dashboard-edit-toolbar .MuiButton-root.mythic-dashboard-table-action:hover {
|
||||
background-color: ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.16)} !important;
|
||||
border-color: ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.42)} !important;
|
||||
}
|
||||
.mythic-dashboard-edit-toolbar .MuiButton-root.mythic-table-row-action-hover-info:hover {
|
||||
background-color: ${(props) => props.theme.palette.info.main + "2b"} !important;
|
||||
border-color: ${(props) => props.theme.palette.info.main + "88"} !important;
|
||||
color: ${(props) => props.theme.palette.info.main} !important;
|
||||
}
|
||||
.mythic-dashboard-edit-toolbar .MuiButton-root.mythic-dashboard-table-action-hover-danger:hover {
|
||||
background-color: ${(props) => props.theme.palette.error.main + "2b"} !important;
|
||||
border-color: ${(props) => props.theme.palette.error.main + "88"} !important;
|
||||
color: ${(props) => props.theme.palette.error.main} !important;
|
||||
}
|
||||
.mythic-dashboard-widget-dialog {
|
||||
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.default};
|
||||
min-height: 28rem;
|
||||
}
|
||||
.mythic-dashboard-widget-dialog-header {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.surfaces?.raised || props.theme.palette.background.paper};
|
||||
background-image: ${(props) => {
|
||||
const headerTextColor = 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: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.75rem;
|
||||
min-width: 0;
|
||||
padding: 0.7rem 0.8rem;
|
||||
}
|
||||
.mythic-dashboard-widget-dialog-title {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.9rem;
|
||||
font-weight: 850;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.mythic-dashboard-widget-dialog-subtitle {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.74rem;
|
||||
font-weight: 650;
|
||||
line-height: 1.25;
|
||||
margin-top: 0.18rem;
|
||||
}
|
||||
.mythic-dashboard-widget-grid {
|
||||
display: grid;
|
||||
gap: 0.65rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-widget-option {
|
||||
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: ${(props) => props.theme.palette.mode === "dark" ? "inset 0 1px 0 rgba(255,255,255,0.04)" : "0 1px 2px rgba(15,23,42,0.05)"};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
min-height: 6.4rem;
|
||||
min-width: 0;
|
||||
padding: 0.7rem;
|
||||
transition: background-color 140ms ease, border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
|
||||
}
|
||||
.mythic-dashboard-widget-option:hover,
|
||||
.mythic-dashboard-widget-option:focus-visible {
|
||||
background-color: ${(props) => props.theme.palette.primary.main + "10"};
|
||||
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.5 : 0.34)};
|
||||
box-shadow: ${(props) => props.theme.palette.mode === "dark" ? `inset 0 1px 0 ${alpha(props.theme.palette.common.white, 0.055)}, 0 8px 18px ${alpha(props.theme.palette.common.black, 0.18)}` : `0 8px 18px ${alpha(props.theme.palette.common.black, 0.08)}`};
|
||||
outline: none;
|
||||
}
|
||||
.mythic-dashboard-widget-option-selected {
|
||||
background-image: ${(props) => `linear-gradient(135deg, ${alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)} 0%, ${alpha(props.theme.palette.background.paper, 0)} 60%)`};
|
||||
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.68 : 0.48)};
|
||||
}
|
||||
.mythic-dashboard-widget-option-top {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
}
|
||||
.mythic-dashboard-widget-option-title {
|
||||
color: ${(props) => props.theme.palette.text.primary};
|
||||
font-size: 0.84rem;
|
||||
font-weight: 820;
|
||||
line-height: 1.2;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-dashboard-widget-option-summary {
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.mythic-dashboard-widget-category {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.primary.main + "18"};
|
||||
border: 1px solid ${(props) => props.theme.palette.primary.main + "45"};
|
||||
border-radius: 999px;
|
||||
color: ${(props) => props.theme.palette.primary.main};
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.64rem;
|
||||
font-weight: 850;
|
||||
line-height: 1;
|
||||
padding: 0.24rem 0.42rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mythic-dashboard-loading-overlay {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(0,0,0,0.45)" : "rgba(255,255,255,0.55)"};
|
||||
@@ -3629,6 +4241,31 @@ tspan {
|
||||
font-weight: 750;
|
||||
padding: 0.75rem 0.9rem;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.mythic-dashboard-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.mythic-dashboard-card,
|
||||
.mythic-dashboard-card-wide,
|
||||
.mythic-dashboard-card-table {
|
||||
grid-column: span 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
.mythic-dashboard-perspective-toggle {
|
||||
width: 100%;
|
||||
}
|
||||
.mythic-dashboard-perspective-toggle .MuiToggleButton-root {
|
||||
flex: 1 1 0;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
.mythic-dashboard-edit-toolbar {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
.mythic-dashboard-edit-toolbar-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
.mythic-form-code-editor {
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
@@ -5543,10 +6180,11 @@ tspan {
|
||||
}
|
||||
.mythic-eventing-flow-node-action {
|
||||
align-items: center;
|
||||
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.035)"};
|
||||
background-color: ${(props) => props.theme.palette.info.main + "16"};
|
||||
border-color: ${(props) => props.theme.palette.info.main + "45"};
|
||||
color: ${(props) => props.theme.palette.info.main};
|
||||
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
|
||||
border-radius: ${(props) => props.theme.shape.borderRadius}px;
|
||||
color: ${(props) => props.theme.palette.text.secondary};
|
||||
display: inline-flex;
|
||||
font-size: 0.66rem;
|
||||
font-weight: 750;
|
||||
|
||||
Reference in New Issue
Block a user