Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions tavern/internal/www/build/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"files": {
"main.css": "/static/css/main.ceda9797.css",
"main.js": "/static/js/main.1807ec02.js",
"main.js": "/static/js/main.3bb1ad6b.js",
"react-syntax-highlighter/refractor-core-import.js": "/static/js/react-syntax-highlighter/refractor-core-import.d0cd1e85.chunk.js",
"react-syntax-highlighter_languages_refractor_abap.js": "/static/js/react-syntax-highlighter_languages_refractor_abap.a2bf84e3.chunk.js",
"react-syntax-highlighter_languages_refractor_actionscript.js": "/static/js/react-syntax-highlighter_languages_refractor_actionscript.fff5a604.chunk.js",
Expand Down Expand Up @@ -158,7 +158,7 @@
"static/media/eldrich.png": "/static/media/eldrich.a80c74e8249d2461e174.png",
"index.html": "/index.html",
"main.ceda9797.css.map": "/static/css/main.ceda9797.css.map",
"main.1807ec02.js.map": "/static/js/main.1807ec02.js.map",
"main.3bb1ad6b.js.map": "/static/js/main.3bb1ad6b.js.map",
"refractor-core-import.d0cd1e85.chunk.js.map": "/static/js/react-syntax-highlighter/refractor-core-import.d0cd1e85.chunk.js.map",
"react-syntax-highlighter_languages_refractor_abap.a2bf84e3.chunk.js.map": "/static/js/react-syntax-highlighter_languages_refractor_abap.a2bf84e3.chunk.js.map",
"react-syntax-highlighter_languages_refractor_actionscript.fff5a604.chunk.js.map": "/static/js/react-syntax-highlighter_languages_refractor_actionscript.fff5a604.chunk.js.map",
Expand Down Expand Up @@ -315,6 +315,6 @@
},
"entrypoints": [
"static/css/main.ceda9797.css",
"static/js/main.1807ec02.js"
"static/js/main.3bb1ad6b.js"
]
}
2 changes: 1 addition & 1 deletion tavern/internal/www/build/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/site.webmanifest"><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Realm - Red Team Engagement Platform</title><script defer="defer" src="/static/js/main.1807ec02.js"></script><link href="/static/css/main.ceda9797.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/site.webmanifest"><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Realm - Red Team Engagement Platform</title><script defer="defer" src="/static/js/main.3bb1ad6b.js"></script><link href="/static/css/main.ceda9797.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
3 changes: 0 additions & 3 deletions tavern/internal/www/build/static/js/main.1807ec02.js

This file was deleted.

1 change: 0 additions & 1 deletion tavern/internal/www/build/static/js/main.1807ec02.js.map

This file was deleted.

3 changes: 3 additions & 0 deletions tavern/internal/www/build/static/js/main.3bb1ad6b.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tavern/internal/www/build/static/js/main.3bb1ad6b.js.map

Large diffs are not rendered by default.

45 changes: 29 additions & 16 deletions tavern/internal/www/src/components/TaskStatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,50 @@
import { RepeatClockIcon, TimeIcon } from "@chakra-ui/icons";
import { Badge } from "@chakra-ui/react";
import { CheckCircleIcon } from "@heroicons/react/24/outline";
import { CheckCircleIcon, ExclamationCircleIcon } from "@heroicons/react/24/outline";

type Props = {
task: any;
}
const TaskStatusBadge = (props: Props) => {
const {task} = props;
const { task } = props;

if(task.execFinishedAt){
if (task.error.length > 0) {
return (
<div>
<Badge fontSize='0.8em' size="large" colorScheme="red" variant="solid">
<div className="flex flex-row gap-1 justify-center items-center p-1" >
<ExclamationCircleIcon className="w-5" color="white" />
<div>Error</div>
</div>
</Badge>
</div>
)
}

if (task.execFinishedAt) {
return (
<div>
<Badge fontSize='0.8em' size="large" colorScheme="green" variant="solid">
<div className="flex flex-row gap-1 justify-center items-center p-1" >
<CheckCircleIcon className="w-5" color="white"/>
<CheckCircleIcon className="w-5" color="white" />
<div>Finished</div>
</div>
</Badge>
</div>
);
}

if(task.execStartedAt){
return (
<div>
<Badge fontSize='0.8em' size="large" colorScheme="gray" variant="outline">
<div className="flex flex-row gap-1 justify-center items-center p-1" >
<RepeatClockIcon w={4} h={4} color="gray"/>
<div>In-Progress</div>
</div>
</Badge>
</div>
);
if (task.execStartedAt) {
return (
<div>
<Badge fontSize='0.8em' size="large" colorScheme="gray" variant="outline">
<div className="flex flex-row gap-1 justify-center items-center p-1" >
<RepeatClockIcon w={4} h={4} color="gray" />
<div>In-Progress</div>
</div>
</Badge>
</div>
);
}

return (
Expand All @@ -45,4 +58,4 @@ const TaskStatusBadge = (props: Props) => {
</div>
);
}
export default TaskStatusBadge;
export default TaskStatusBadge;
20 changes: 20 additions & 0 deletions tavern/internal/www/src/features/task-output/ErrorWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";
import { CodeBlock, tomorrow } from "react-code-blocks";

const ErrorWrapper = ({ error }: { error: string }) => {
return (
<div className="flex flex-col gap-2">
<h3 className="text-2xl text-gray-800">Error</h3>
<div className="bg-gray-200 rounded-md p-0.5 ">
<CodeBlock
text={error}
language={""}
showLineNumbers={false}
theme={tomorrow}
codeBlock
/>
</div>
</div>
);
}
export default ErrorWrapper;
5 changes: 5 additions & 0 deletions tavern/internal/www/src/features/task-output/TaskOutput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import BeaconTile from "../../components/BeaconTile";
import TomeAccordion from "../../components/TomeAccordion";
import { Image } from "@chakra-ui/react";
import OutputWrapper from "./OutputWrapper";
import { CodeBlock, tomorrow } from "react-code-blocks";
import ErrorWrapper from "./ErrorWrapper";

type Props = {
isOpen: boolean,
Expand Down Expand Up @@ -113,6 +115,9 @@ export const TaskOutput = (props: Props) => {
</div>
</div>
)}
{selectedTask && selectedTask.error.length > 0 &&
<ErrorWrapper error={selectedTask.error} />
}
{selectedTask && selectedTask?.id && <OutputWrapper id={selectedTask.id} />}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BarChart, Bar, Rectangle, XAxis, YAxis, CartesianGrid, Tooltip, Legend,

import { EmptyState, EmptyStateType } from '../../../components/tavern-base-ui/EmptyState';
import { TomeTag } from '../../../utils/consts';
import { TaskChartKeys } from '../../../utils/enums';
import { getOfflineOnlineStatus } from '../../../utils/utils';


Expand All @@ -16,7 +17,7 @@ const GroupBarChart = ({ data, loading, hosts }: { data: Array<any>, loading: bo
}

const height = data.length * 40 < 320 ? 320 : data.length * 40;
const groupWithFewestTasks = data.length > 0 ? data[0] : {};
const groupWithFewestTasks = data.length > 0 ? data.find((task: any) => task.name !== "undefined") : null;

const getTotalActiveBeaconsForGroup = () => {
const returnedValue = hosts.reduce((acc, curr) => {
Expand All @@ -30,8 +31,6 @@ const GroupBarChart = ({ data, loading, hosts }: { data: Array<any>, loading: bo
return returnedValue;
};

const activeBeaconForGroupWithFewestTasks = getTotalActiveBeaconsForGroup();

const handleClickQuestDetails = (item: any) => {
navigation("/tasks", {
state: [{
Expand Down Expand Up @@ -83,7 +82,7 @@ const GroupBarChart = ({ data, loading, hosts }: { data: Array<any>, loading: bo
<YAxis type="category" dataKey="name" width={100} interval={0} />
<Tooltip />
<Legend />
<Bar dataKey="task count" fill="#553C9A" onClick={handleBarClick} activeBar={<Rectangle fill="#805AD5" stroke="#322659" />}>
<Bar stackId="a" dataKey={TaskChartKeys.taskNoError} fill="#553C9A" onClick={handleBarClick} activeBar={<Rectangle fill="#805AD5" stroke="#322659" />}>
{data.map((_, index) => (
<Cell
cursor="pointer"
Expand All @@ -93,14 +92,24 @@ const GroupBarChart = ({ data, loading, hosts }: { data: Array<any>, loading: bo
/>
))}
</Bar>
<Bar stackId="a" dataKey={TaskChartKeys.taskError} fill="#E53E3E" onClick={handleBarClick} activeBar={<Rectangle fill="#F56565" stroke="#822727" />}>
{data.map((_, index) => (
<Cell
cursor="pointer"
fill="#E53E3E"
stroke="#E53E3E"
key={`bar-cell-group-task-error-${index}`}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
{groupWithFewestTasks.name !== "undefined" &&
{groupWithFewestTasks &&
<div className='flex flex-col border-l-4 border-purple-900 px-4 py-2 rounded'>
<h4 className="font-semibold text-gray-900">Consider targeting the group with fewest tasks</h4>
<p className='text-sm'>{groupWithFewestTasks.name} has {groupWithFewestTasks["task count"]} task run and {activeBeaconForGroupWithFewestTasks} online beacons</p>
<p className='text-sm'>{groupWithFewestTasks.name} has {groupWithFewestTasks[TaskChartKeys.taskNoError]} task run and {getTotalActiveBeaconsForGroup()} online beacons</p>
<div className='flex flex-row gap-4 mt-2'>
<Button size="sm" variant="link" colorScheme="purple" onClick={() => {
handleClickQuestDetails(groupWithFewestTasks)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const QuestCard = ({ formattedData, loading }: { formattedData: any, loading: bo
<DashboardStatistic label="Total quests" value={formattedData.totalQuests} loading={loading} />
<DashboardStatistic label="Total tasks" value={formattedData.totalTasks} loading={loading} />
<DashboardStatistic label="Total outputs" value={formattedData.totalOutput} loading={loading} />
<DashboardStatistic label="Total errors" value={formattedData.totalErrors} loading={loading} />
</div>
<div className="col-span-1 md:col-span-4">
<TaskBarChart data={formattedData?.taskTimelime || []} taskTactics={formattedData.taskTactics} loading={loading} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { BarChart, Bar, Rectangle, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';

import { EmptyState, EmptyStateType } from '../../../components/tavern-base-ui/EmptyState';
import { TaskChartKeys } from '../../../utils/enums';


const TomeBarChart = ({ data, loading }: { data: Array<any>, loading: boolean }) => {
Expand Down Expand Up @@ -37,7 +38,8 @@ const TomeBarChart = ({ data, loading }: { data: Array<any>, loading: boolean })
<YAxis type="category" dataKey="name" width={300} interval={0} />
<Tooltip />
<Legend />
<Bar dataKey="task count" fill="#553C9A" activeBar={<Rectangle fill="#805AD5" stroke="#322659" />} />
<Bar stackId="a" dataKey={TaskChartKeys.taskNoError} fill="#553C9A" activeBar={<Rectangle fill="#805AD5" stroke="#322659" />} />
<Bar stackId="a" dataKey={TaskChartKeys.taskError} fill="#E53E3E" activeBar={<Rectangle fill="#F56565" stroke="#822727" />} />
</BarChart>
</ResponsiveContainer>
</div>
Expand Down
70 changes: 37 additions & 33 deletions tavern/internal/www/src/pages/dashboard/hook/useOverviewData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { differenceInMinutes, format, startOfHour } from "date-fns";
import { useCallback, useEffect, useState } from "react";

import { TomeTag } from "../../../utils/consts";
import { TaskChartKeys } from "../../../utils/enums";

type UniqueCountObject = {
type UniqueTaskCountObject = {
[key: string] : {
name: string,
value: number,
id: any
name: string;
tasksError: number;
tasksNoError: number;
id: any;
}
}

export const useOverviewData = (data: Array<any>) => {
const [loading, setLoading] = useState(false);
const [formattedData, setFormattedData] = useState({
Expand All @@ -20,39 +21,47 @@ export const useOverviewData = (data: Array<any>) => {
groupUsage: [],
totalQuests: 0,
totalOutput: 0,
totalTasks: 0
}) as Array<any>;
totalTasks: 0,
totalErrors: 0
}) as any;


const applyUniqueTermCount = useCallback((term: string, id: any, termCountObject: UniqueCountObject)=> {
const applyUniqueTermCount = useCallback((term: string, id: any, hasError: boolean, termCountObject: UniqueTaskCountObject)=> {
if (!(term in termCountObject)) {
return termCountObject[term] = {
name: term,
value: 1,
tasksNoError: hasError ? 0 : 1,
tasksError: hasError ? 1 : 0,
id: id
};
}
else {
return termCountObject[term].value += 1;
if(hasError){
return termCountObject[term].tasksError += 1
}
else{
return termCountObject[term].tasksNoError += 1
}
}
},[]);

const getTermUsage = useCallback((termCountObject: UniqueCountObject, sortByAsc: boolean) => {
const getTermUsage = useCallback((termCountObject: UniqueTaskCountObject, sortByAsc: boolean) => {
const keys = Object.keys(termCountObject);
const dataUsage = [];

for (let key in keys) {
dataUsage.push({
name: keys[key],
"task count": termCountObject[keys[key]].value,
[TaskChartKeys.taskError]: termCountObject[keys[key]].tasksError,
[TaskChartKeys.taskNoError]: termCountObject[keys[key]].tasksNoError,
"id": termCountObject[keys[key]].id
});
}
if(sortByAsc){
dataUsage.sort((a, b) => b["task count"] - a["task count"]);
dataUsage.sort((a, b) => b[TaskChartKeys.taskNoError] - a[TaskChartKeys.taskNoError]);
}
else{
dataUsage.sort((a, b) => a["task count"] - b["task count"]);
dataUsage.sort((a, b) => a[TaskChartKeys.taskNoError] - b[TaskChartKeys.taskNoError]);
}
return dataUsage;
},[]);
Expand All @@ -65,7 +74,7 @@ export const useOverviewData = (data: Array<any>) => {
tasksTimeline.push({
label: format(created, "iii haaa"),
timestamp: startOfHour(created),
"tasks created": 1,
[TaskChartKeys.taskCreated]: 1,
[tactic]:1,
})
}
Expand All @@ -76,13 +85,13 @@ export const useOverviewData = (data: Array<any>) => {
tasksTimeline.push({
label: format(created, "iii haaa"),
timestamp: startOfHour(created),
"tasks created": 1,
[TaskChartKeys.taskCreated]: 1,
[tactic]: 1,
});

}
else{
tasksTimeline[tasksTimeline.length -1]["tasks created"] += 1;
tasksTimeline[tasksTimeline.length -1][TaskChartKeys.taskCreated] += 1;

if(tactic in tasksTimeline[tasksTimeline.length -1]){
tasksTimeline[tasksTimeline.length -1][tactic] += 1;
Expand All @@ -104,32 +113,26 @@ export const useOverviewData = (data: Array<any>) => {
return
},[]);

const modifyTaskWithOutput = useCallback((task: any, tasksWithOutput: number) => {
const hasOuput = task?.node?.outputSize > 0;
if(hasOuput){
return tasksWithOutput += 1;
}
else{
return tasksWithOutput
}
},[]);

const formatOverviewData = useCallback((data: Array<any>) =>{
const uniqueQuestCount = {} as any;
const uniqueTomeCount = {} as any;
const uniqueTactics = {} as any;
const uniqueGroup = {} as any;
const tasksTimeline = [] as Array<any>;
let tasksWithOutput = 0;
let tasksWithError = 0;

for (let index in data){
const groupTag = data[index]?.node?.beacon?.host?.tags.find( (tag: TomeTag) => tag.kind === "group");
applyUniqueTermCount(data[index]?.node?.quest?.id, data[index]?.node?.quest?.id, uniqueQuestCount);
applyUniqueTermCount(groupTag?.name, groupTag?.id, uniqueGroup);
applyUniqueTermCount(data[index]?.node?.quest?.tome?.name, data[index]?.node?.quest?.tome.id, uniqueTomeCount);
applyUniqueTermCount(data[index]?.node?.quest?.id, data[index]?.node?.quest?.id, false, uniqueQuestCount);
applyUniqueTermCount(groupTag?.name, groupTag?.id, data[index]?.node?.error.length > 0, uniqueGroup);
applyUniqueTermCount(data[index]?.node?.quest?.tome?.name, data[index]?.node?.quest?.tome.id, data[index]?.node?.error.length > 0, uniqueTomeCount);
modifyTaskTimeline(data[index], tasksTimeline);
modifyUniqueTactics(data[index], uniqueTactics);
tasksWithOutput = modifyTaskWithOutput(data[index], tasksWithOutput);

if (data[index]?.node?.outputSize > 0) tasksWithOutput += 1;
if (data[index]?.node?.error?.length > 0) tasksWithError += 1;

}

const overviewData = {
Expand All @@ -139,13 +142,14 @@ export const useOverviewData = (data: Array<any>) => {
groupUsage: getTermUsage(uniqueGroup, false),
totalQuests: Object.keys(uniqueQuestCount).length,
totalOutput: tasksWithOutput,
totalTasks: data.length
totalTasks: data.length,
totalErrors: tasksWithError
}
setLoading(false);

setFormattedData(overviewData);

},[getTermUsage, applyUniqueTermCount, modifyTaskTimeline, modifyTaskWithOutput, modifyUniqueTactics]);
},[getTermUsage, applyUniqueTermCount, modifyTaskTimeline, modifyUniqueTactics]);

useEffect(()=> {
formatOverviewData(data);
Expand Down
Loading