diff --git a/client/src/components/Card.tsx b/client/src/components/Card.tsx index cb4f642..58c3634 100644 --- a/client/src/components/Card.tsx +++ b/client/src/components/Card.tsx @@ -1,6 +1,16 @@ import React from 'react'; import { CardProps } from '../types'; +/** + * Card component that displays a card with a title and recharts chart content as children. + * + * @param {Object} props - The properties object. + * @param {string} props.title - The title of the card. + * @param {React.ReactNode} props.children - The chart content to be displayed inside the card. + * @param {boolean} props.isDarkMode - Flag to determine if dark mode should be applied. + * + * @returns {JSX.Element} The rendered Card component. + */ const Card: React.FC = ({ title, children, isDarkMode }) => { return (
diff --git a/client/src/components/EventCard.tsx b/client/src/components/EventCard.tsx index d4f1a36..4a147ad 100644 --- a/client/src/components/EventCard.tsx +++ b/client/src/components/EventCard.tsx @@ -1,5 +1,19 @@ import React from 'react'; -import { EventCardProps } from '../types'; // Ensure this matches the updated structure +import { EventCardProps } from '../types'; +/** + * EventCard component displays information about an event and provides a button to view more details. + * + * @component + * @param {Object} props - The props object. + * @param {Object} props.event - The event object containing details about the event. + * @param {string} props.event.name - The name of the event. + * @param {string} props.event.time - The timestamp of the event. + * @param {string} props.event.username - The username of the person associated with the event. + * @param {Function} props.onViewDetails - Callback function to handle viewing event details. + * + * @returns {JSX.Element} The rendered EventCard component. + */ + const EventCard: React.FC = ({ event, onViewDetails }) => { // Ensure event.time is a valid Date object diff --git a/client/src/components/IpAccessCombined.tsx b/client/src/components/IpAccessCombined.tsx index 30bc7eb..63c48ef 100644 --- a/client/src/components/IpAccessCombined.tsx +++ b/client/src/components/IpAccessCombined.tsx @@ -1,16 +1,45 @@ import { useState, useEffect } from 'react'; import AccessPerIpChart from './charts/AccessPerIp'; import IpAccessOverTimeChart from './charts/IpAccessOverTime'; -import { CountedEvent, IPLocation, IpAccessCombinedProps } from '../types'; // Import the interface from types.ts +import { CountedEvent, IPLocation, IpAccessCombinedProps } from '../types'; +/** + * IpAccessCombined component fetches and displays IP location counts and details for a selected IP. + * + * @component + * @param {Object} props - The component props. + * @param {string} props.currentIp - The currently selected IP address. + * @param {Function} props.setCurrentIp - Function to update the currently selected IP address. + * @returns {JSX.Element} The rendered component. + * + * @typedef {Object} IPLocation + * @property {string} city - The city of the IP location. + * @property {string} region - The region of the IP location. + * @property {string} country - The country of the IP location. + * @property {string} source_ip - The source IP address. + * + * @typedef {Object} CountedEvent + * @property {number} count - The count of events for the IP. + * + * @typedef {Object} IpAccessCombinedProps + * @property {string} currentIp - The currently selected IP address. + * @property {Function} setCurrentIp - Function to update the currently selected IP address. + * + * @typedef {IPLocation & CountedEvent} IPLocationCountedEvent + * + * @example + * + */ export default function IpAccessCombined({ currentIp, setCurrentIp, }: IpAccessCombinedProps): JSX.Element { + // State to hold the IP location counts const [ipLocCounts, setIpLocCounts] = useState<(IPLocation & CountedEvent)[]>( [] ); + // Fetch IP location counts when the component mounts useEffect(() => { fetch('/events?countOn=source_ip&includeLocation=true') .then((response) => { @@ -25,6 +54,7 @@ export default function IpAccessCombined({ ); }, []); + // Find the location details of the current IP const currentIpLoc = ipLocCounts.find( ({ source_ip }) => source_ip === currentIp ); @@ -32,6 +62,7 @@ export default function IpAccessCombined({ return ( <>
+ {/* Render the AccessPerIpChart component */} {currentIp && ( <> + {/* Display the location details of the current IP */}

Location: {currentIpLoc?.city}, {currentIpLoc?.region},{' '} {currentIpLoc?.country}

- {/* Make sure the chart renders only when IP is selected */} + {/* TODO: Render the IpAccessOverTimeChart component only when an IP is selected//possible display styling issue */} )} diff --git a/client/src/components/Modal.tsx b/client/src/components/Modal.tsx index 116e9a2..2f4aaa0 100644 --- a/client/src/components/Modal.tsx +++ b/client/src/components/Modal.tsx @@ -1,23 +1,39 @@ import React from 'react'; import { ModalProps } from '../types'; +// Modal component definition +/** + * Modal component to display event details in a modal dialog. + * + * @param {Object} props - The properties object. + * @param {boolean} props.isOpen - Indicates if the modal is open. + * @param {function} props.onClose - Function to call when the modal is closed. + * @param {Object} props.event - The event object containing event details. + * @param {boolean} props.isDarkMode - Indicates if dark mode is enabled. + * + * @returns {JSX.Element | null} The Modal component or null if not open or no event. + */ const Modal: React.FC = ({ isOpen, onClose, event, isDarkMode, }) => { + // If the modal is not open or there is no event, return null if (!isOpen || !event) return null; return ( + // Modal overlay that closes the modal when clicked
+ {/* Modal content container */}
e.stopPropagation()} > + {/* Modal header with title and close button */}

Event Details

+ {/* Modal content displaying event details */}

Event Type: {event.type ?? 'N/A'} @@ -47,12 +64,14 @@ const Modal: React.FC = ({

Raw JSON Data:

+ {/* Container for displaying raw JSON data of specified event log*/}
{JSON.stringify(event, null, 2)}
+ {/* Modal footer with close button */}
+ {/* Dropdown toggle button */}
= ({ : 'USER'}
+ {/* Dropdown menu */} {dropdownOpen && (
diff --git a/client/src/components/charts/AccessPerIp.tsx b/client/src/components/charts/AccessPerIp.tsx index dbb38cc..add250b 100644 --- a/client/src/components/charts/AccessPerIp.tsx +++ b/client/src/components/charts/AccessPerIp.tsx @@ -2,8 +2,34 @@ import { useState, useEffect } from 'react'; import { Cell, Legend, Pie, PieChart } from 'recharts'; import { CountedEvent, IPLocation } from '../../types'; +// Define colors for the pie chart slices const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042']; +/** + * Component to render a pie chart displaying access counts per IP location. + * + * @param {Object} props - The component props. + * @param {string} [props.currentIp] - The currently selected IP address. + * @param {React.Dispatch>} props.setCurrentIp - Function to set the currently selected IP address. + * @param {Array} props.ipLocCounts - Array of IP location counts. + * @returns {JSX.Element} The rendered pie chart component. + * + * @component + * @example + * const currentIp = "192.168.1.1"; + * const setCurrentIp = (ip) => console.log(ip); + * const ipLocCounts = [ + * { source_ip: "192.168.1.1", count: 10, location: "Location A" }, + * { source_ip: "192.168.1.2", count: 5, location: "Location B" } + * ]; + * return ( + * + * ); + */ export default function AccessPerIpChart({ currentIp, setCurrentIp, @@ -13,7 +39,7 @@ export default function AccessPerIpChart({ setCurrentIp: React.Dispatch>; ipLocCounts: (IPLocation & CountedEvent)[]; }): JSX.Element { - const [loading, setLoading] = useState(true); // Add loading state + const [loading, setLoading] = useState(true); useEffect(() => { // Simulate loading delay for data @@ -24,6 +50,8 @@ export default function AccessPerIpChart({ }, [ipLocCounts]); const RADIAN = Math.PI / 180; + + // Function to render custom labels inside the pie chart slices const renderCustomizedLabel = ({ cx, cy, diff --git a/client/src/components/charts/EventSource.tsx b/client/src/components/charts/EventSource.tsx index 31205fe..5b5fa91 100644 --- a/client/src/components/charts/EventSource.tsx +++ b/client/src/components/charts/EventSource.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { Bar, BarChart, LabelList, XAxis, Cell } from 'recharts'; import { CountedEvent } from '../../types'; +// Array of colors for the chart const COLORS = [ '#0088FE', '#00C49F', @@ -12,13 +13,46 @@ const COLORS = [ '#FFCC99', ]; +/** + * EventSourceChart component renders a bar chart of event sources. + * + * @component + * @example + * return ( + * + * ) + * + * @returns {JSX.Element} The rendered bar chart component. + * + * @remarks + * This component fetches event data from the server and displays it in a bar chart. + * It also allows users to click on a bar to select an event source, which is displayed below the chart. + * + * @typedef {Object} CountedEvent + * @property {string} source - The source of the event. + * @property {number} count - The count of events from this source. + * + * @state {CountedEvent[]} events - The fetched events data. + * @state {boolean} loading - The loading status of the data fetch. + * @state {string | null} selectedEventSource - The selected event source when a bar is clicked. + * + * @hook {useEffect} Fetches events data on component mount. + * + * @function handleClick + * @description Handles click event on a bar to toggle the selected event source. + * @param {string} source - The source of the event that was clicked. + */ export default function EventSourceChart() { + // State to store the fetched events data const [events, setEvents] = useState([]); + // State to manage loading status const [loading, setLoading] = useState(true); + // State to manage the selected event source when a bar is clicked const [selectedEventSource, setSelectedEventSource] = useState( null - ); // State for clicked event source + ); + // Fetch events data on component mount useEffect(() => { setLoading(true); fetch('/events?countOn=source') @@ -35,8 +69,10 @@ export default function EventSourceChart() { ); }, []); + // Display loading message while data is being fetched if (loading) return

Loading chart...

; + // Handle click event on a bar to toggle the selected event source const handleClick = (source: string) => { setSelectedEventSource((prevSelected) => prevSelected === source ? null : source @@ -46,7 +82,7 @@ export default function EventSourceChart() { return (
( handleClick(entry.source)} // Attach the click handler to each Cell style={{ cursor: 'pointer' }} // Add a pointer cursor to indicate clickability /> diff --git a/client/src/components/charts/EventType.tsx b/client/src/components/charts/EventType.tsx index 0aa3d55..1b672a6 100644 --- a/client/src/components/charts/EventType.tsx +++ b/client/src/components/charts/EventType.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { Bar, BarChart, LabelList, XAxis, Cell } from 'recharts'; import { CountedEvent } from '../../types'; +// Define an array of colors to be used for the bars in the chart const COLORS = [ '#0088FE', '#00C49F', @@ -12,13 +13,46 @@ const COLORS = [ '#FFCC99', ]; +/** + * EventTypeChart component displays a bar chart of event counts fetched from the server. + * It allows users to select an event by clicking on a bar, and displays the selected event name. + * + * @component + * @example + * return ( + * + * ) + * + * @returns {JSX.Element} The rendered bar chart component. + * + * @remarks + * - The component fetches event data from the server when it mounts. + * - It displays a loading message while the data is being fetched. + * - Event names are formatted by adding spaces before capital letters. + * - The chart's width is dynamically adjusted based on the number of events. + * - Clicking on a bar toggles the selection of an event. + * + * @typedef {Object} CountedEvent - The structure of an event data object. + * @property {string} name - The name of the event. + * @property {number} count - The count of the event. + * + * @typedef {Object} EventData + * @property {string} name - The name of the event. + * + * @typedef {Object} FetchError + * @property {string} err - The error message. + */ export default function EventTypeChart() { + // State to store the fetched events data const [events, setEvents] = useState([]); + // State to manage the loading state const [loading, setLoading] = useState(true); + // State to store the name of the selected event const [selectedEventName, setSelectedEventName] = useState( null - ); // State for clicked event name + ); + // Fetch the events data when the component mounts useEffect(() => { setLoading(true); fetch('/events?countOn=name') @@ -27,6 +61,7 @@ export default function EventTypeChart() { throw new Error(response.status + ': ' + response.statusText); }) .then((data: CountedEvent[] | { err: string }) => { + // Format the event names by adding spaces before capital letters setEvents( (data as CountedEvent[]).map((event) => ({ ...event, @@ -40,10 +75,11 @@ export default function EventTypeChart() { ); }, []); + // Display a loading message while the data is being fetched if (loading) return

Loading chart...

; + // Handle click event on a bar to toggle the selection of an event const handleClick = (data: { name: string }) => { - // Toggle selection: if already selected, deselect; otherwise, select setSelectedEventName((prevSelected) => prevSelected === data.name ? null : data.name ); @@ -52,7 +88,7 @@ export default function EventTypeChart() { return (
handleClick(data)} - fill={COLORS[index % COLORS.length]} + fill={COLORS[index % COLORS.length]} // Assign color to each bar /> ))} + * ``` + * + * @typedef {Object} GeoJSONFeatureCollection + * @property {string} type - The type of the GeoJSON object. + * @property {Array} features - The array of GeoJSON features. + * + * @typedef {Object} IPLocation + * @property {string} source_ip - The source IP address. + * @property {number} lat - The latitude of the IP location. + * @property {number} long - The longitude of the IP location. + * + * @typedef {Object} CountedEvent + * @property {number} count - The count of events for the IP location. + * + * @typedef {IPLocation & CountedEvent} IPLocationWithCount + * + * @state {GeoJSONFeatureCollection | null} geoJSON - The state to store the GeoJSON data. + * @state {IPLocationWithCount[]} ipData - The state to store the IP location data with event counts. + * + * @function + * @name fetchGeoJSON + * @description Fetches the GeoJSON data for the map. + * + * @function + * @name fetchEventData + * @description Fetches the event data with IP locations and counts. + */ const HeatMap: React.FC = () => { + // State to store the GeoJSON data const [geoJSON, setGeoJSON] = useState(null); + // State to store the IP location data with event counts const [ipData, setIpData] = useState<(IPLocation & CountedEvent)[]>([]); useEffect(() => { + // Fetch the GeoJSON data for the map fetch( 'https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson' ) @@ -24,6 +67,7 @@ const HeatMap: React.FC = () => { .then((data: GeoJSONFeatureCollection) => setGeoJSON(data)) .catch((error) => console.error('Error fetching geoJSON:', error)); + // Fetch the event data with IP locations and counts fetch('/events?countOn=source_ip&includeLocation=true') .then((response) => { if (response.ok) return response.json(); @@ -48,6 +92,7 @@ const HeatMap: React.FC = () => { {({ geographies }) => + // Render each geography (country) on the map geographies.map((geo) => ( { } {ipData.map(({ source_ip, lat, long, count }) => ( + // Render a marker for each IP location with event count + * ) + * + * @remarks + * This component fetches data from the server and displays it in an area chart. + * It shows a loading message while the data is being fetched. + * + * @function + * @name IpAccessOverTimeChart + */ export default function IpAccessOverTimeChart({ currentIp, }: { currentIp?: string; }): JSX.Element | null { + // State to store the fetched IP times data const [ipTimes, setIpTimes] = useState([]); - const [loading, setLoading] = useState(true); // Add loading state + // State to manage loading status + const [loading, setLoading] = useState(true); + // Effect to fetch data when the component mounts or currentIp changes useEffect(() => { setLoading(true); // Set loading to true before fetching data fetch('/events?countOn=time&groupTimeBy=minute') @@ -18,27 +42,30 @@ export default function IpAccessOverTimeChart({ throw new Error(response.status + ': ' + response.statusText); }) .then((data: CountedEvent[] | { err: string }) => { - setIpTimes(() => data as CountedEvent[]); - setLoading(false); // Set loading to true before fetching data + setIpTimes(() => data as CountedEvent[]); // Update state with fetched data + setLoading(false); // Set loading to false after data is fetched }) .catch((error) => console.warn('IpAccessOverTime: fetch error: ' + error) ); }, [currentIp]); - if (!currentIp) return null; // Return null instead of undefined + // Return null if currentIp is not provided + if (!currentIp) return null; + // Display loading message while data is being fetched if (loading) return

Loading chart...

; - //reversed the times to show the most recent first + + // Render the AreaChart with the fetched data return ( - - + {/* X-axis with reversed order to show the most recent first */} + {/* Y-axis */} ); diff --git a/client/src/components/charts/PieChart.tsx b/client/src/components/charts/PieChart.tsx deleted file mode 100644 index debdbf3..0000000 --- a/client/src/components/charts/PieChart.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { PieChart, Pie, Cell, Tooltip, Legend } from 'recharts'; - -interface DataPoint { - name: string; - value: number; -} - -const initialData: DataPoint[] = [ - { name: 'Sadness', value: 400 }, - { name: 'Anger', value: 300 }, - { name: 'Frustration', value: 300 }, - { name: 'Depression', value: 200 }, -]; - -const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042']; - -const TestPieChart: React.FC = () => { - const [data, setData] = useState(initialData); - - useEffect(() => { - const intervalId = setInterval(() => { - setData(prevData => - prevData.map(point => ({ - ...point, - value: Math.floor(Math.random() * 500), - })) - ); - }, 5000); - - return () => clearInterval(intervalId); - }, []); - - return ( - - name} // Using name directly - outerRadius={80} - fill="#8884d8" - dataKey="value" - > - {data.map((_, index) => ( - - ))} - - - - - ); -}; - -export default TestPieChart; - diff --git a/client/src/components/charts/UserActivity.tsx b/client/src/components/charts/UserActivity.tsx index 73edf4b..9f4088d 100644 --- a/client/src/components/charts/UserActivity.tsx +++ b/client/src/components/charts/UserActivity.tsx @@ -9,7 +9,32 @@ import { } from 'recharts'; import { SimplifiedEvent } from '../../types'; -const UserActivityChart: React.FC = () => { +/** + * UserActivityChart is a React functional component that renders an area chart + * displaying user activity over time. It fetches event data from the server, + * processes it, and displays it as an area chart using the Recharts library. + * + * @component + * @example + * return ( + * + * ) + * + * @returns {JSX.Element} The rendered area chart component. + * + * @remarks + * The component fetches event data from the server endpoint '/events?countOn=time&groupTimeBy=minute'. + * The data is expected to be an array of objects with 'time' and 'count' properties. + * The 'time' property is converted to a local time string and stored in the component's state. + * + * The X-axis of the chart is formatted to display the day of the week and the month/day. + * The Y-axis is limited to a domain of [0, 50] with 6 ticks. + * + * @function + * @name UserActivityChart + */ +const UserActivityChart: React.FC = (): JSX.Element => { + // State to hold the fetched event data const [data, setData] = useState([]); useEffect(() => { @@ -19,6 +44,7 @@ const UserActivityChart: React.FC = () => { throw new Error(response.status + ': ' + response.statusText); }) .then((data: { time: string; count: number }[]) => + // Map the fetched data to the desired format and update the state setData( (data as { time: string; count: number }[]).map((event) => ({ localTime: new Date(event.time).toLocaleString(), @@ -46,7 +72,9 @@ const UserActivityChart: React.FC = () => { data={data} margin={{ top: 10, right: 30, left: 0, bottom: 50 }} > + {/* Grid lines for the chart */} + {/* X-axis configuration */} { angle={-45} textAnchor="end" /> + {/* Y-axis configuration */} + {/* Tooltip to show data on hover */} + {/* Area chart configuration */} ); diff --git a/client/src/pages/EventsDashboard.tsx b/client/src/pages/EventsDashboard.tsx index c8686e9..6b2dd7e 100644 --- a/client/src/pages/EventsDashboard.tsx +++ b/client/src/pages/EventsDashboard.tsx @@ -3,9 +3,25 @@ import Modal from '../components/Modal'; import { EventsDashboardProps, TGEvent } from '../types'; import EventCard from '../components/EventCard'; -// const EventCard = lazy(() => import('../components/EventCard')); -// const Modal = lazy(() => import('../components/Modal')); - +/** + * EventsDashboard component displays a list of recent events on event-cards and allows users to view event details in a modal. + * + * @param {EventsDashboardProps} props - The props for the EventsDashboard component. + * @param {boolean} props.isDarkMode - A boolean indicating if dark mode is enabled. + * + * @returns {JSX.Element} The rendered EventsDashboard component. + * + * @remarks + * - Fetches events data from the server when the component mounts. + * - Handles loading and error states during data fetching. + * - Maps the fetched events to EventCard components for display. + * - Allows users to open a modal to view details of a selected event. + * + * @example + * ```tsx + * + * ``` + */ const EventsDashboard: React.FC = ({ isDarkMode }) => { const [modalOpen, setModalOpen] = useState(false); const [selectedEvent, setSelectedEvent] = useState(null); @@ -13,6 +29,7 @@ const EventsDashboard: React.FC = ({ isDarkMode }) => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + // Fetch events data from the server when the component mounts useEffect(() => { fetch('/events') .then((response) => { @@ -29,11 +46,13 @@ const EventsDashboard: React.FC = ({ isDarkMode }) => { setLoading(false); }, []); + // Function to open the modal and set the selected event const handleOpenModal = (event: TGEvent): void => { setSelectedEvent(event); setModalOpen(true); }; + // Function to close the modal and clear the selected event const handleCloseModal = (): void => { setModalOpen(false); setSelectedEvent(null);