diff --git a/src/components/dashboard/default/TabCard/ExpandedRow.tsx b/src/components/dashboard/default/TabCard/ExpandedRow.tsx
new file mode 100644
index 0000000..24f11da
--- /dev/null
+++ b/src/components/dashboard/default/TabCard/ExpandedRow.tsx
@@ -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) => {text},
+ },
+ {
+ 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(null);
+ const [logData, setLogData] = useState(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 (
+
+
+ {error.rule && }
+
+
+
+ {error.log && }
+
+
+
+ );
+};
diff --git a/src/components/dashboard/default/TabCard/SecurityEventsTable.tsx b/src/components/dashboard/default/TabCard/SecurityEventsTable.tsx
new file mode 100644
index 0000000..99d137d
--- /dev/null
+++ b/src/components/dashboard/default/TabCard/SecurityEventsTable.tsx
@@ -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 = [
+ {
+ 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 (
+
+ {_}
+
+ );
+ },
+ },
+];
+
+export const SecurityEventsTable = ({
+ data,
+ loading,
+}: {
+ data: MismatchesItem[];
+ loading: boolean;
+}) => {
+ return (
+ ,
+ }}
+ className="overflow-scroll"
+ loading={loading}
+ pagination={{
+ pageSize: 10,
+ showSizeChanger: true,
+ pageSizeOptions: ['10', '20', '50', '100'],
+ }}
+ locale={{ emptyText: loading ? 'Loading...' : 'No data found' }}
+ />
+ );
+};
diff --git a/src/pages/dashboards/Charts/LineChart.tsx b/src/pages/dashboards/Charts/LineChart.tsx
new file mode 100644
index 0000000..90940e4
--- /dev/null
+++ b/src/pages/dashboards/Charts/LineChart.tsx
@@ -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 ;
+};
diff --git a/src/pages/dashboards/MitreAttackChart.tsx b/src/pages/dashboards/Charts/MitreAttackChart.tsx
similarity index 97%
rename from src/pages/dashboards/MitreAttackChart.tsx
rename to src/pages/dashboards/Charts/MitreAttackChart.tsx
index 69164b6..e46c982 100644
--- a/src/pages/dashboards/MitreAttackChart.tsx
+++ b/src/pages/dashboards/Charts/MitreAttackChart.tsx
@@ -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[];
diff --git a/src/pages/dashboards/Default.tsx b/src/pages/dashboards/Default.tsx
index 1436de2..4303ed0 100644
--- a/src/pages/dashboards/Default.tsx
+++ b/src/pages/dashboards/Default.tsx
@@ -11,7 +11,6 @@ import {
Tag,
TagProps,
} from 'antd';
-import { Card, PageHeader } from '../../components';
import {
HomeOutlined,
PieChartOutlined,
@@ -19,12 +18,10 @@ import {
WarningOutlined,
} from '@ant-design/icons';
import { Helmet } from 'react-helmet-async';
-import { CSSProperties, ReactNode, useEffect, useState } from 'react';
+import { ReactNode, useEffect, useState } from 'react';
import {
fetchBarList,
- fetchMismatchesRule,
fetchMismatchesTable,
- fetchMismatchesLog,
fetchMismatchesChart,
} from '../../service/default';
import {
@@ -32,34 +29,13 @@ import {
MismatchesItem,
MismatchesLevelChart,
MismatchesResponse,
- SigmaRule,
} from '../../types/default';
-import MitreAttackPieChart from './MitreAttackChart';
-import { fetchDeviceList } from '../../service/device_list';
import { DeviceListData } from '../../types/device_list';
-import { ColumnsType } from 'antd/es/table';
-import { NetworkEvent } from '../../types/event_logs';
-import {
- Chart as ChartJS,
- CategoryScale,
- LinearScale,
- PointElement,
- LineElement,
- Title,
- Legend,
- ArcElement,
-} from 'chart.js';
-import { Line } from 'react-chartjs-2';
-
-ChartJS.register(
- CategoryScale,
- LinearScale,
- PointElement,
- LineElement,
- Title,
- Legend,
- ArcElement
-);
+import { Card, PageHeader } from '../../components';
+import { fetchDeviceList } from '../../service/device_list';
+import MitreAttackPieChart from './Charts/MitreAttackChart';
+import { MismatchesLevelLineChart } from './Charts/LineChart';
+import { SecurityEventsTable } from '../../components/dashboard/default/TabCard/SecurityEventsTable';
const { TabPane } = Tabs;
@@ -90,194 +66,11 @@ const SECURITY_TABS = [
},
];
-const EVENT_COLUMNS: ColumnsType = [
- {
- 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 (
-
- {_}
-
- );
- },
- },
-];
-
-const ExpandedRow = ({ record }: { record: MismatchesItem }) => {
- const [ruleData, setRuleData] = useState(null);
- const [logData, setLogData] = useState(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 VERTICAL_TABLE_COLUMNS = [
- {
- title: 'Field',
- dataIndex: 'field',
- key: 'field',
- width: '30%',
- render: (text: string) => {text},
- },
- {
- title: 'Value',
- dataIndex: 'value',
- key: 'value',
- width: '70%',
- render: (value: unknown) => {
- if (typeof value === 'object' && value !== null)
- return JSON.stringify(value);
- return String(value);
- },
- },
- ];
-
- 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 (
-
-
- {error.rule && }
-
-
-
- {error.log && }
-
-
-
- );
-};
-
const POPOVER_BUTTON_PROPS: ButtonProps = {
type: 'text',
};
-const cardStyles: CSSProperties = {
+const cardStyles = {
height: '100%',
};
@@ -330,58 +123,6 @@ const PRODUCTS_COLUMNS = [
},
];
-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 ;
-};
-
export const DefaultDashboardPage = () => {
const [activeTab, setActiveTab] = useState('all');
const [barData, setBarData] = useState([]);
@@ -608,22 +349,7 @@ export const DefaultDashboardPage = () => {
))}
- ,
- }}
- className="overflow-scroll"
- loading={loading}
- pagination={{
- pageSize: 10,
- showSizeChanger: true,
- pageSizeOptions: ['10', '20', '50', '100'],
- }}
- locale={{ emptyText: loading ? 'Loading...' : 'No data found' }}
- />
+
diff --git a/src/pages/dashboards/LineChart.tsx b/src/pages/dashboards/LineChart.tsx
deleted file mode 100644
index a2f9e6e..0000000
--- a/src/pages/dashboards/LineChart.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-'use client';
-
-import {
- LineChart as RechartsLineChart,
- Line,
- CartesianGrid,
- XAxis,
- YAxis,
- Tooltip,
- ResponsiveContainer,
-} from 'recharts';
-import { Card } from 'antd';
-
-interface LineChartProps {
- data: { date: string; value: number }[];
- title: string;
-}
-
-export const LineChart = ({ data, title }: LineChartProps) => {
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-};