diff --git a/app/api/get-connection-logs/route.ts b/app/api/get-connection-logs/route.ts index 7b85aee..cb8443d 100644 --- a/app/api/get-connection-logs/route.ts +++ b/app/api/get-connection-logs/route.ts @@ -1,4 +1,4 @@ -import { allConnectionStatusType } from '@/app/components/dashboard/Dashboard.server'; +import { connectionLogsList } from '@/app/components/dashboard/Dashboard.server'; import { db } from '@/lib/db'; export async function GET(request: Request) { @@ -7,10 +7,10 @@ export async function GET(request: Request) { if (!daysParam || daysParam === '' || isNaN(parseInt(daysParam))) { const yesterday = Date.now() - 86400000; - const allConnectionStatus = db + const requestedConnectionLogs = db .prepare('SELECT * FROM connectionlogs WHERE time > ? ORDER BY id DESC') - .all(yesterday) as allConnectionStatusType; - return new Response(JSON.stringify(allConnectionStatus), { + .all(yesterday) as connectionLogsList; + return new Response(JSON.stringify(requestedConnectionLogs), { status: 200, headers: { 'Content-Type': 'application/json' @@ -21,11 +21,11 @@ export async function GET(request: Request) { const days = parseInt(daysParam); const daysInEpoch = days * 86400000; const daysToSearch = Date.now() - daysInEpoch; - const allConnectionStatus = db + const requestedConnectionLogs = db .prepare(`SELECT * FROM connectionlogs WHERE time > ? ORDER BY id DESC`) - .all(daysToSearch) as allConnectionStatusType; + .all(daysToSearch) as connectionLogsList; - return new Response(JSON.stringify(allConnectionStatus), { + return new Response(JSON.stringify(requestedConnectionLogs), { status: 200, headers: { 'Content-Type': 'application/json' diff --git a/app/components/dashboard/ConnectionLogsListModal.client.tsx b/app/components/dashboard/ConnectionLogsListModal.client.tsx new file mode 100644 index 0000000..7a1f45f --- /dev/null +++ b/app/components/dashboard/ConnectionLogsListModal.client.tsx @@ -0,0 +1,99 @@ +import { connectionLogsListToHumanFormat } from '@/lib/logs-list-to-human-format'; +import { useState, useEffect } from 'react'; +import { connectionLogsList } from './Dashboard.server'; + +export default function ConnectionLogsListModal({ + toggleConnectionLogsListModal, + connectionLogsListModalRef +}: { + toggleConnectionLogsListModal: () => void; + connectionLogsListModalRef: React.RefObject; +}) { + const [days, setDays] = useState(1); + return ( + +
+

Connection Logs

+

Number of days to show: {days}

+ setDays(parseInt(e.target.value))} + /> + + +
+
+ +
+
+ ); +} + +function ConnectionLogsList({ days }: { days: number }) { + const [connectionLogsListBody, setConnectionLogsListBody] = useState< + JSX.Element[] + >([]); + const [humanReadableDisconnectedTime, setHumanReadableDisconnectedTime] = + useState(''); + let listBody: JSX.Element[] = []; + + async function updateLogsList(days: number) { + const connectionLogsList = (await ( + await fetch(`/api/get-connection-logs?days=${days}`) + ).json()) as connectionLogsList; + connectionLogsList.forEach((status) => { + listBody.push( + + {status.id} + + {status.status === 'connected' ? 'Connected' : 'Disconnected'} + + + {new Date(status.time).toLocaleString('en-US', { + day: '2-digit', + month: 'short', + hour: 'numeric', + minute: '2-digit', + second: '2-digit' + })} + + + ); + }); + setConnectionLogsListBody(listBody); + setHumanReadableDisconnectedTime( + connectionLogsListToHumanFormat(connectionLogsList, days) + ); + } + useEffect(() => { + updateLogsList(days); + }, [days]); + return ( +
+ {humanReadableDisconnectedTime !== 'No Disconnects' ? ( +
+ {humanReadableDisconnectedTime} +
+ ) : null} + + + + + + + + + {connectionLogsListBody} +
IDStatusTime
+
+ ); +} diff --git a/app/components/dashboard/Dashboard.server.tsx b/app/components/dashboard/Dashboard.server.tsx index f25deeb..7d63777 100644 --- a/app/components/dashboard/Dashboard.server.tsx +++ b/app/components/dashboard/Dashboard.server.tsx @@ -5,7 +5,7 @@ import DashboardCardTotalDisconnectTime from './DashboardCardTotalDisconnectTime import SpeedMeter from './SpeedMeter.client'; import { getUptime } from '@/lib/get-uptime'; -export type allConnectionStatusType = { +export type connectionLogsList = { id: number; status: string; time: number; @@ -13,23 +13,22 @@ export type allConnectionStatusType = { export default function Dashboard() { let now = Date.now(); let yesterday = now - 86400000; - let isWithinTimeFrame = true; - let allConnectionStatus = db + let allConnectionLogsFromServer = db .prepare('SELECT * FROM connectionlogs WHERE time > ? ORDER BY id DESC') - .all(yesterday) as allConnectionStatusType; - if (allConnectionStatus.length === 0) { - allConnectionStatus = db + .all(yesterday) as connectionLogsList; + if (allConnectionLogsFromServer.length === 0) { + allConnectionLogsFromServer = db .prepare('SELECT * FROM connectionlogs ORDER BY id DESC LIMIT 1') - .all() as allConnectionStatusType; - isWithinTimeFrame = false; + .all() as connectionLogsList; } return ( <> - + ); diff --git a/app/components/dashboard/DashboardCardCurrentStatus.client.tsx b/app/components/dashboard/DashboardCardCurrentStatus.client.tsx index 68fdca2..c525c55 100644 --- a/app/components/dashboard/DashboardCardCurrentStatus.client.tsx +++ b/app/components/dashboard/DashboardCardCurrentStatus.client.tsx @@ -1,11 +1,11 @@ 'use client'; import DashboardCardBase from './DashboardBase.server'; -import { allConnectionStatusType } from './Dashboard.server'; +import { connectionLogsList } from './Dashboard.server'; import { useEffect, useMemo, useState } from 'react'; export function DashboardCardCurrentStatus({ allConnectionStatus }: { - allConnectionStatus: allConnectionStatusType; + allConnectionStatus: connectionLogsList; }) { const [isConnected, setIsConnected] = useState(false); let lastStatus = allConnectionStatus[0]; @@ -29,7 +29,7 @@ export function DashboardCardCurrentStatus({ ); } -function returnStatusBool(status: allConnectionStatusType[0]) { +function returnStatusBool(status: connectionLogsList[0]) { if (!status) { return false; } else { diff --git a/app/components/dashboard/DashboardCardTotalDisconnectTime.client.tsx b/app/components/dashboard/DashboardCardTotalDisconnectTime.client.tsx index d31b988..b868eb6 100644 --- a/app/components/dashboard/DashboardCardTotalDisconnectTime.client.tsx +++ b/app/components/dashboard/DashboardCardTotalDisconnectTime.client.tsx @@ -1,21 +1,21 @@ 'use client'; import { useEffect, useRef, useState } from 'react'; -import { allConnectionStatusType } from './Dashboard.server'; +import { connectionLogsList } from './Dashboard.server'; import DashboardCardBase from './DashboardBase.server'; +import ConnectionLogsListModal from './ConnectionLogsListModal.client'; import Image from 'next/image'; +import { connectionLogsListToHumanFormat } from '@/lib/logs-list-to-human-format'; export default function DashboardCardTotalDisconnectTime({ - allConnectionStatus, - isWithinTimeFrame + allConnectionLogs }: { - allConnectionStatus: allConnectionStatusType; - isWithinTimeFrame: boolean; + allConnectionLogs: connectionLogsList; }) { - const [totalDisconnectedTime, setTotalDisconnectedTime] = + const [humanReadableDisconnectedTime, setHumanReadableDisconnectedTime] = useState('Loading...'); const [backgroundColor, setBackgroundColor] = useState( - dashboardColor(allConnectionStatus) + dashboardColor(allConnectionLogs) ); let connectionLogsListModalRef = useRef(null); function toggleConnectionLogsListModal() { @@ -26,16 +26,16 @@ export default function DashboardCardTotalDisconnectTime({ } } useEffect(() => { - setTotalDisconnectedTime( - parseAllConnectionStatus(allConnectionStatus, isWithinTimeFrame) + setHumanReadableDisconnectedTime( + connectionLogsListToHumanFormat(allConnectionLogs) ); - setBackgroundColor(dashboardColor(allConnectionStatus)); + setBackgroundColor(dashboardColor(allConnectionLogs)); }, []); return ( <>
- {totalDisconnectedTime} in 24 hours + {humanReadableDisconnectedTime} in 24 hours void; - connectionLogsListModalRef: React.RefObject; -}) { - const [days, setDays] = useState(1); - return ( - -
-

Connection Logs

-

Number of days to show: {days}

- setDays(parseInt(e.target.value))} - /> - - -
-
- -
-
- ); -} - -function ConnectionLogsList({ days }: { days: number }) { - const [connectionLogsListBody, setConnectionLogsListBody] = useState< - JSX.Element[] - >([]); - const [totalDisconnectedTime, setTotalDisconnectedTime] = useState(''); - let listBody: JSX.Element[] = []; - - async function updateLogsList(days: number) { - const connectionLogsList = (await ( - await fetch(`/api/get-connection-logs?days=${days}`) - ).json()) as allConnectionStatusType; - connectionLogsList.forEach((status) => { - listBody.push( - - {status.id} - - {status.status === 'connected' ? 'Connected' : 'Disconnected'} - - - {new Date(status.time).toLocaleString('en-US', { - day: '2-digit', - month: 'short', - hour: 'numeric', - minute: '2-digit', - second: '2-digit' - })} - - - ); - }); - setConnectionLogsListBody(listBody); - setTotalDisconnectedTime( - parseAllConnectionStatus(connectionLogsList, true, days) - ); - } - useEffect(() => { - updateLogsList(days); - }, [days]); - return ( -
- {totalDisconnectedTime !== 'No Disconnects' ? ( -
- {totalDisconnectedTime} -
- ) : null} - - - - - - - - - {connectionLogsListBody} -
IDStatusTime
-
- ); -} - -function dashboardColor(allConnectionStatus: allConnectionStatusType) { +function dashboardColor(allConnectionLogs: connectionLogsList) { let backgroundColor = 'bg-emerald-700'; - allConnectionStatus.forEach((status) => { + allConnectionLogs.forEach((status) => { if (status.status === 'disconnected') { backgroundColor = 'bg-red-800'; } }); return backgroundColor; } - -function parseAllConnectionStatus( - allConnectionStatus: allConnectionStatusType, - isWithinTimeFrame: boolean, - totalDays = 1 -) { - if (allConnectionStatus.length === 0) { - return 'No Disconnects'; - } - allConnectionStatus = allConnectionStatus.reverse(); - if (!isWithinTimeFrame) { - if ( - allConnectionStatus[allConnectionStatus.length - 1].status === - 'disconnected' - ) { - return `Bro it be dead since - ${new Date( - allConnectionStatus[allConnectionStatus.length - 1].time - ).toLocaleString('en-US', { - day: '2-digit', - month: 'short', - hour: 'numeric', - minute: '2-digit' - })}`; - } - return 'No Disconnects'; - } - - let totalDisconnects = 0; - for (let i = 0; i < allConnectionStatus.length; i++) { - if (allConnectionStatus[i].status === 'disconnected') { - totalDisconnects++; - } - } - - let totalDisconnectedTime = 0; - let isConnected = false; - let lastStatusChangeTime = 0; - - // If the first status is connected, add time from start of total days to until first connect - if (allConnectionStatus[0].status === 'connected') { - if (allConnectionStatus[0].id !== 1) { - const totalDaysInMS = totalDays * 86400000; - const startTime = Date.now() - totalDaysInMS; // This basically creates a virtual disconnect at the start of the list - totalDisconnectedTime += allConnectionStatus[0].time - startTime; - } - } - - allConnectionStatus.forEach((status) => { - if (status.status === 'disconnected' && !isConnected) { - // Mark the start of a disconnected period - lastStatusChangeTime = status.time; - isConnected = true; - } else if (status.status === 'connected' && isConnected) { - // Calculate and accumulate disconnected time - totalDisconnectedTime += status.time - lastStatusChangeTime; - isConnected = false; - } - }); - - // If the last status was disconnected, add the time until now - if (isConnected) { - totalDisconnectedTime += Date.now() - lastStatusChangeTime; - } - - if (totalDisconnects === 0) { - return 'No Disconnects'; - } - return `${totalDisconnectsToString( - totalDisconnects - )} with ${millisecondsToReadableTime(totalDisconnectedTime)} of downtime`; -} - -function totalDisconnectsToString(totalDisconnects: number) { - if (totalDisconnects > 1) { - return `Disconnected ${totalDisconnects} times`; - } else if (totalDisconnects === 1) { - return `Disconnected 1 time`; - } -} - -function millisecondsToReadableTime(milliseconds: number) { - // Define constants for time units in milliseconds - const msPerSecond = 1000; - const msPerMinute = 60 * msPerSecond; - const msPerHour = msPerMinute * 60; - const msPerDay = msPerHour * 24; - - // Calculate the number of days, hours, minutes, and seconds - const days = Math.floor(milliseconds / msPerDay); - milliseconds %= msPerDay; - const hours = Math.floor(milliseconds / msPerHour); - milliseconds %= msPerHour; - const minutes = Math.floor(milliseconds / msPerMinute); - milliseconds %= msPerMinute; - const seconds = Math.floor(milliseconds / msPerSecond); - - // Format a unit with "s" when plural - const formatUnit = (value: number, unit: string) => { - if (value === 1) { - return `${value} ${unit}`; - } - return `${value} ${unit}s`; - }; - - // Initialize an array to store the formatted time parts - const parts = []; - - // Add units to the array as needed, pluralized when appropriate - if (days > 0) { - parts.push(formatUnit(days, 'day')); - } - if (hours > 0) { - parts.push(formatUnit(hours, 'hour')); - } - if (minutes > 0) { - parts.push(formatUnit(minutes, 'minute')); - } - if (seconds > 0) { - parts.push(formatUnit(seconds, 'second')); - } - - // Join the formatted time parts into a human-readable string - return parts.join(', '); -} diff --git a/lib/logs-list-to-human-format.ts b/lib/logs-list-to-human-format.ts new file mode 100644 index 0000000..818403e --- /dev/null +++ b/lib/logs-list-to-human-format.ts @@ -0,0 +1,136 @@ +import { connectionLogsList } from '@/app/components/dashboard/Dashboard.server'; + +export function connectionLogsListToHumanFormat( + allConnectionLogs: connectionLogsList, + totalDays = 1 +) { + if (allConnectionLogs.length === 0) { + return 'No Disconnects'; + } + allConnectionLogs = allConnectionLogs.reverse(); + + // Get the last log and check if its within the time frame of totalDays + let isWithinTimeFrame = false; + const lastLog = allConnectionLogs[allConnectionLogs.length - 1]; + const lastLogTime = new Date(lastLog.time).getTime(); + const now = Date.now(); + const timeDifference = now - lastLogTime; + if (timeDifference < totalDays * 86400000) { + isWithinTimeFrame = true; + } + + if (!isWithinTimeFrame) { + if ( + allConnectionLogs[allConnectionLogs.length - 1].status === 'disconnected' + ) { + return `Bro it be dead since - ${new Date( + allConnectionLogs[allConnectionLogs.length - 1].time + ).toLocaleString('en-US', { + day: '2-digit', + month: 'short', + hour: 'numeric', + minute: '2-digit' + })}`; + } + return 'No Disconnects'; + } + + let totalDisconnects = 0; + for (let i = 0; i < allConnectionLogs.length; i++) { + if (allConnectionLogs[i].status === 'disconnected') { + totalDisconnects++; + } + } + + let totalDowntimeInMS = 0; + let isConnected = false; + let lastStatusChangeTime = 0; + + // If the first status is connected, add time from start of total days to until first connect + if (allConnectionLogs[0].status === 'connected') { + if (allConnectionLogs[0].id !== 1) { + const totalDaysInMS = totalDays * 86400000; + const startTime = Date.now() - totalDaysInMS; // This basically creates a virtual disconnect at the start of the list + totalDowntimeInMS += allConnectionLogs[0].time - startTime; + } + } + + allConnectionLogs.forEach((status) => { + if (status.status === 'disconnected' && !isConnected) { + // Mark the start of a disconnected period + lastStatusChangeTime = status.time; + isConnected = true; + } else if (status.status === 'connected' && isConnected) { + // Calculate and accumulate disconnected time + totalDowntimeInMS += status.time - lastStatusChangeTime; + isConnected = false; + } + }); + + // If the last status was disconnected, add the time until now + if (isConnected) { + totalDowntimeInMS += Date.now() - lastStatusChangeTime; + } + + // This will happen when there is no disconnect within the time frame of totalDays but there is a downtime + if (totalDisconnects === 0 && totalDowntimeInMS === 0) { + totalDisconnects = 1; + } + const totalDisconnectsString = totalDisconnectsToString(totalDisconnects); + const totalDowntimeString = millisecondsToReadableTime(totalDowntimeInMS); + + return `${totalDisconnectsString} with ${totalDowntimeString} of downtime`; +} + +function totalDisconnectsToString(totalDisconnects: number) { + if (totalDisconnects > 1) { + return `Disconnected ${totalDisconnects} times`; + } else if (totalDisconnects === 1) { + return `Disconnected 1 time`; + } +} + +function millisecondsToReadableTime(milliseconds: number) { + // Define constants for time units in milliseconds + const msPerSecond = 1000; + const msPerMinute = 60 * msPerSecond; + const msPerHour = msPerMinute * 60; + const msPerDay = msPerHour * 24; + + // Calculate the number of days, hours, minutes, and seconds + const days = Math.floor(milliseconds / msPerDay); + milliseconds %= msPerDay; + const hours = Math.floor(milliseconds / msPerHour); + milliseconds %= msPerHour; + const minutes = Math.floor(milliseconds / msPerMinute); + milliseconds %= msPerMinute; + const seconds = Math.floor(milliseconds / msPerSecond); + + // Format a unit with "s" when plural + const formatUnit = (value: number, unit: string) => { + if (value === 1) { + return `${value} ${unit}`; + } + return `${value} ${unit}s`; + }; + + // Initialize an array to store the formatted time parts + const parts = []; + + // Add units to the array as needed, pluralized when appropriate + if (days > 0) { + parts.push(formatUnit(days, 'day')); + } + if (hours > 0) { + parts.push(formatUnit(hours, 'hour')); + } + if (minutes > 0) { + parts.push(formatUnit(minutes, 'minute')); + } + if (seconds > 0) { + parts.push(formatUnit(seconds, 'second')); + } + + // Join the formatted time parts into a human-readable string + return parts.join(', '); +}