diff --git a/frontend/src/components/common/LogViewer.stories.tsx b/frontend/src/components/common/LogViewer.stories.tsx index ebae1636bbb..4f68436c1a1 100644 --- a/frontend/src/components/common/LogViewer.stories.tsx +++ b/frontend/src/components/common/LogViewer.stories.tsx @@ -1,4 +1,7 @@ +import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'; +import { action } from '@storybook/addon-actions'; import { Meta, StoryFn } from '@storybook/react'; +import { useCallback, useEffect, useState } from 'react'; import { LogViewer, LogViewerProps } from './LogViewer'; export default { @@ -11,10 +14,252 @@ export default { const Template: StoryFn = args => ; -export const SomeLogs = Template.bind({}); -SomeLogs.args = { - logs: ['one log\n', 'two log\n', 'three log\n', 'four\n'], - title: 'Some logs', - downloadName: 'a-file-of-logs.txt', +export const BasicLogs = Template.bind({}); +BasicLogs.args = { + logs: ['first log entry\n', 'second log entry\n', 'third log entry\n', 'end of log stream'], + title: 'Basic Logs', + downloadName: 'basic-logs', open: true, }; +BasicLogs.parameters = { + docs: { + description: { + story: 'LogViewer displaying simple text logs.', + }, + }, +}; + +export const ColoredLogs = Template.bind({}); +ColoredLogs.args = { + logs: [ + '\x1b[32mINFO\x1b[0m: Application started\n', + '\x1b[33mWARN\x1b[0m: High memory usage detected\n', + '\x1b[31mERROR\x1b[0m: Failed to connect to database\n', + '\x1b[36mDEBUG\x1b[0m: Connection attempt details...\n', + ], + title: 'Colored Logs', + downloadName: 'colored-logs', + open: true, +}; +ColoredLogs.parameters = { + docs: { + description: { + story: 'LogViewer displaying logs with ANSI color codes for different log levels.', + }, + }, +}; + +export const LogOverflow = Template.bind({}); +LogOverflow.args = { + logs: Array.from( + { length: 1000 }, + (_, i) => + `[${new Date().toISOString()}] (log #${ + i + 1 + }): from container 'database' log line log line log line log line log line log line log line log line log line\n` + ), + title: 'Log Overflow To Test Scrolling Behaviour Performance', + downloadName: 'log-overflow', + open: true, +}; +LogOverflow.parameters = { + docs: { + description: { + story: + 'LogViewer handling a large number of log entries to test scrolling behavior performance.', + }, + }, +}; + +export const LiveUpdatingLogs = () => { + const [logs, setLogs] = useState(['Starting log stream\n']); + const [counter, setCounter] = useState(1); + + useEffect(() => { + const interval = setInterval(() => { + const timestamp = new Date().toISOString(); + setLogs(prevLogs => [...prevLogs, `[${timestamp}] New log entry: #${counter}\n`]); + setCounter(prevCounter => prevCounter + 1); + }, 500); + + return () => clearInterval(interval); + }, [counter]); + + return ( + + ); +}; +LiveUpdatingLogs.parameters = { + docs: { + description: { + story: 'LogViewer demonstrating live-updating logs with timestamps.', + }, + }, +}; + +const containerLogs: Record = { + 'web-server': [ + '[web-server] Server starting on port 3000\n', + '[web-server] Connected to database\n', + '[web-server] Handling incoming request\n', + ], + database: [ + '[database] PostgreSQL database initialized\n', + '[database] Running migrations\n', + '[database] Creating initial tables\n', + ], + cache: [ + '[cache] Redis cache started\n', + '[cache] Cache warm-up complete\n', + '[cache] Processing cache invalidation\n', + ], +}; +const ContainerSelector = ({ + selectedContainer, + onContainerChange, +}: { + selectedContainer: string; + onContainerChange: (container: string) => void; +}) => { + return ( + + + Container + + + + ); +}; +export const TopActions = () => { + const [selectedContainer, setSelectedContainer] = useState('web-server'); + const [logs, setLogs] = useState(containerLogs['web-server']); + + const handleContainerChange = (container: string) => { + setSelectedContainer(container); + setLogs(containerLogs[container]); + action('container-changed')(container); + }; + + return ( + , + ]} + /> + ); +}; +TopActions.parameters = { + docs: { + description: { + story: 'LogViewer displaying custom top actions.', + }, + }, +}; + +const initialLogs = ['[system] Connection established\n', '[system] Starting log stream\n']; +type ConnectionState = 'connected' | 'disconnected' | 'connecting'; +export const ReconnectToSeeLogs = () => { + const [logs, setLogs] = useState(initialLogs); + const [connectionState, setConnectionState] = useState('connected'); + const [reconnectAttempt, setReconnectAttempt] = useState(0); + + useEffect(() => { + if (connectionState !== 'connected') return; + + const logInterval = setInterval(() => { + setLogs(prevLogs => [ + ...prevLogs.slice(-99), // keep last 100 logs + `[app] Log entry at ${new Date().toISOString()}\n`, + ]); + }, 1500); + + return () => clearInterval(logInterval); + }, [connectionState]); + + useEffect(() => { + if (connectionState !== 'connected') return; + + const disconnectTimeout = setTimeout(() => { + if (connectionState === 'connected') { + setConnectionState('disconnected'); + setLogs(prevLogs => [ + ...prevLogs, + '[system] Connection lost. Click reconnect to try again.\n', + ]); + } + }, 5000); // disconnect after 5 seconds + + return () => clearTimeout(disconnectTimeout); + }, [connectionState]); + + const handleReconnect = useCallback(() => { + setConnectionState('connecting'); + setLogs(prev => [...prev, '[system] Attempting to reconnect...\n']); + + setReconnectAttempt(prev => prev + 1); + + setTimeout(() => { + const success = Math.random() > 0.5; // 50% chance of success + setConnectionState(success ? 'connected' : 'disconnected'); + + setLogs(prevLogs => [ + ...prevLogs.slice(-99), // keep last 100 logs + success + ? '[system] Successfully reconnected!\n[system] Resuming log stream\n' + : `[system] Reconnection attempt ${ + reconnectAttempt + 1 + } failed.\n[system] Please try again.\n`, + ]); + }, 2000); // network delay + }, [reconnectAttempt]); + + return ( + + ); +}; +ReconnectToSeeLogs.parameters = { + docs: { + description: { + story: 'LogViewer simulating recovery of connection loss upon clicking on reconnect button.', + }, + }, +}; diff --git a/frontend/src/components/common/__snapshots__/LogViewer.BasicLogs.stories.storyshot b/frontend/src/components/common/__snapshots__/LogViewer.BasicLogs.stories.storyshot new file mode 100644 index 00000000000..3c407ba1467 --- /dev/null +++ b/frontend/src/components/common/__snapshots__/LogViewer.BasicLogs.stories.storyshot @@ -0,0 +1,326 @@ + +