From a4a6643a458c19b5984246e5c3cb7ba807b56835 Mon Sep 17 00:00:00 2001 From: Numonov01 Date: Thu, 29 May 2025 13:11:11 +0500 Subject: [PATCH] default page changed and two pages added --- .../dashboard/default/TabCard/ExpandedRow.tsx | 148 +++++++++ .../default/TabCard/SecurityEventsTable.tsx | 77 +++++ src/pages/dashboards/Charts/LineChart.tsx | 76 +++++ .../{ => Charts}/MitreAttackChart.tsx | 2 +- src/pages/dashboards/Default.tsx | 290 +----------------- src/pages/dashboards/LineChart.tsx | 42 --- 6 files changed, 310 insertions(+), 325 deletions(-) create mode 100644 src/components/dashboard/default/TabCard/ExpandedRow.tsx create mode 100644 src/components/dashboard/default/TabCard/SecurityEventsTable.tsx create mode 100644 src/pages/dashboards/Charts/LineChart.tsx rename src/pages/dashboards/{ => Charts}/MitreAttackChart.tsx (97%) delete mode 100644 src/pages/dashboards/LineChart.tsx 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 ( - - - - - - - - - - - - ); -};