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
148 changes: 148 additions & 0 deletions src/components/dashboard/default/TabCard/ExpandedRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { Tabs, Table, Alert } from 'antd';
import { ReactNode, useEffect, useState } from 'react';
import { MismatchesItem, SigmaRule } from '../../../../types/default';
import { NetworkEvent } from '../../../../types/event_logs';
import {
fetchMismatchesLog,
fetchMismatchesRule,
} from '../../../../service/default';

const { TabPane } = Tabs;

const VERTICAL_TABLE_COLUMNS = [
{
title: 'Field',
dataIndex: 'field',
key: 'field',
width: '30%',
render: (text: string) => <strong>{text}</strong>,
},
{
title: 'Value',
dataIndex: 'value',
key: 'value',
width: '70%',
render: (value: unknown) => {
if (typeof value === 'object' && value !== null)
return JSON.stringify(value);
return String(value);
},
},
];

export const ExpandedRow = ({ record }: { record: MismatchesItem }) => {
const [ruleData, setRuleData] = useState<SigmaRule | null>(null);
const [logData, setLogData] = useState<NetworkEvent | null>(null);
const [loading, setLoading] = useState({
rule: false,
log: false,
});
const [error, setError] = useState({
rule: null as ReactNode | null,
log: null as ReactNode | null,
});

useEffect(() => {
const fetchData = async () => {
try {
setLoading((prev) => ({ ...prev, rule: true }));
const rule = await fetchMismatchesRule(record.rule.id);
setRuleData(rule);
setError((prev) => ({ ...prev, rule: null }));
} catch (err) {
console.error('Error fetching rule:', err);
setError((prev) => ({
...prev,
rule: err instanceof Error ? err.message : 'Failed to load rule data',
}));
} finally {
setLoading((prev) => ({ ...prev, rule: false }));
}

try {
setLoading((prev) => ({ ...prev, log: true }));
const log = await fetchMismatchesLog(record.log_id);
setLogData(log);
setError((prev) => ({ ...prev, log: null }));
} catch (err) {
console.error('Error fetching log:', err);
setError((prev) => ({
...prev,
log: err instanceof Error ? err.message : 'Failed to load log data',
}));
} finally {
setLoading((prev) => ({ ...prev, log: false }));
}
};

fetchData();
}, [record.rule.id, record.log_id]);

const getRuleData = () => {
if (!ruleData) return [];

return [
{ field: 'Title', value: ruleData.title },
{ field: 'Description', value: ruleData.description },
{ field: 'Status', value: ruleData.status },
{ field: 'Level', value: ruleData.level },
{ field: 'Tags', value: ruleData.tags.join(' => ') },
{ field: 'References', value: ruleData.references.join(' => ') },
{ field: 'Author', value: ruleData.author },
{ field: 'Date', value: ruleData.date },
];
};

const getLogData = () => {
if (!logData) return [];

return [
{ field: 'Event ID', value: logData.EventId },
{ field: 'User', value: logData.Event?.User },
{ field: 'Image', value: logData.Event?.Image },
{ field: 'Company', value: logData.Event?.Company },
{ field: 'Command Line', value: logData.Event?.CommandLine },
{ field: 'Parent Image', value: logData.Event?.ParentImage },
{ field: 'Parent Command Line', value: logData.Event?.ParentCommandLine },
{ field: 'Original File Name', value: logData.Event?.OriginalFileName },
{ field: 'Integrity Level', value: logData.Event?.IntegrityLevel },
{
field: 'UTC Time',
value: logData.Event?.UtcTime
? new Date(logData.Event.UtcTime).toLocaleString()
: null,
},
];
};

return (
<Tabs defaultActiveKey="1">
<TabPane tab="Rule" key="1">
{error.rule && <Alert message={error.rule} type="error" showIcon />}
<Table
columns={VERTICAL_TABLE_COLUMNS}
dataSource={getRuleData()}
pagination={false}
bordered
showHeader={false}
rowKey="field"
loading={loading.rule}
locale={{ emptyText: loading.rule ? 'Loading...' : 'No data found' }}
/>
</TabPane>
<TabPane tab="Log" key="2">
{error.log && <Alert message={error.log} type="error" showIcon />}
<Table
columns={VERTICAL_TABLE_COLUMNS}
dataSource={getLogData()}
pagination={false}
bordered
showHeader={false}
rowKey="field"
loading={loading.log}
locale={{ emptyText: loading.log ? 'Loading...' : 'No data found' }}
/>
</TabPane>
</Tabs>
);
};
77 changes: 77 additions & 0 deletions src/components/dashboard/default/TabCard/SecurityEventsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Table, Tag, TagProps } from 'antd';
import { ColumnsType } from 'antd/es/table';
import { ExpandedRow } from './ExpandedRow';
import { MismatchesItem } from '../../../../types/default';

// eslint-disable-next-line react-refresh/only-export-components
export const EVENT_COLUMNS: ColumnsType<MismatchesItem> = [
{
title: 'Rule id',
dataIndex: ['rule', 'id'],
key: 'id',
},
{
title: 'Device Name',
dataIndex: 'device_name',
key: 'device_name',
},
{
title: 'Title',
dataIndex: ['rule', 'title'],
key: 'title',
},
{
title: 'Log id',
dataIndex: 'log_id',
key: 'log_id',
},
{
title: 'Level',
dataIndex: ['rule', 'level'],
key: 'level',
render: (_: string) => {
let color: TagProps['color'];

if (_ === 'low') {
color = 'cyan';
} else if (_ === 'medium') {
color = 'geekblue';
} else {
color = 'magenta';
}

return (
<Tag color={color} className="text-capitalize">
{_}
</Tag>
);
},
},
];

export const SecurityEventsTable = ({
data,
loading,
}: {
data: MismatchesItem[];
loading: boolean;
}) => {
return (
<Table
dataSource={data}
columns={EVENT_COLUMNS}
rowKey="id"
expandable={{
expandedRowRender: (record) => <ExpandedRow record={record} />,
}}
className="overflow-scroll"
loading={loading}
pagination={{
pageSize: 10,
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}}
locale={{ emptyText: loading ? 'Loading...' : 'No data found' }}
/>
);
};
76 changes: 76 additions & 0 deletions src/pages/dashboards/Charts/LineChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Line } from 'react-chartjs-2';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Legend,
} from 'chart.js';
import { MismatchesLevelChart } from '../../../types/default';

ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Legend
);

export const MismatchesLevelLineChart = ({
data,
}: {
data: MismatchesLevelChart;
}) => {
const chartData = {
labels: data.labels,
datasets: data.datasets.map((dataset) => ({
label: dataset.label,
data: dataset.data,
borderColor:
dataset.label === 'High'
? 'rgb(255, 99, 132)'
: dataset.label === 'Medium'
? 'rgb(54, 162, 235)'
: 'rgb(75, 192, 192)',
backgroundColor:
dataset.label === 'High'
? 'rgba(255, 99, 132, 0.5)'
: dataset.label === 'Medium'
? 'rgba(54, 162, 235, 0.5)'
: 'rgba(75, 192, 192, 0.5)',
tension: 0.1,
})),
};

const options = {
responsive: true,
plugins: {
legend: {
position: 'top' as const,
},
title: {
display: true,
},
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Count',
},
},
x: {
title: {
display: true,
text: 'Time',
},
},
},
};

return <Line options={options} data={chartData} />;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Pie } from '@ant-design/charts';
import { BarData } from '../../types/default';
import { BarData } from '../../../types/default';

interface MitreAttackPieChartProps {
data: BarData[];
Expand Down
Loading
Loading