mirror of
https://github.com/stackblitz/bolt.new
synced 2025-02-06 04:48:04 +00:00
Event logs bug fix
minor improvements download logs, auto scroll, clear logs
This commit is contained in:
parent
e39f16e436
commit
87057f8137
@ -3,14 +3,34 @@ import { useSettings } from '~/lib/hooks/useSettings';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { Switch } from '~/components/ui/Switch';
|
import { Switch } from '~/components/ui/Switch';
|
||||||
import { logStore, type LogEntry } from '~/lib/stores/logs';
|
import { logStore, type LogEntry } from '~/lib/stores/logs';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
|
|
||||||
export default function EventLogsTab() {
|
export default function EventLogsTab() {
|
||||||
const {} = useSettings();
|
const {} = useSettings();
|
||||||
|
const showLogs = useStore(logStore.showLogs);
|
||||||
const [logLevel, setLogLevel] = useState<LogEntry['level']>('info');
|
const [logLevel, setLogLevel] = useState<LogEntry['level']>('info');
|
||||||
const [autoScroll, setAutoScroll] = useState(true);
|
const [autoScroll, setAutoScroll] = useState(true);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [, forceUpdate] = useState({});
|
const [, forceUpdate] = useState({});
|
||||||
|
|
||||||
|
const filteredLogs = useMemo(() => {
|
||||||
|
const logs = logStore.getLogs();
|
||||||
|
return logs.filter((log) => {
|
||||||
|
const matchesLevel = !logLevel || log.level === logLevel;
|
||||||
|
const matchesSearch =
|
||||||
|
!searchQuery ||
|
||||||
|
log.message.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
JSON.stringify(log.details).toLowerCase().includes(searchQuery.toLowerCase());
|
||||||
|
|
||||||
|
return matchesLevel && matchesSearch;
|
||||||
|
});
|
||||||
|
}, [logLevel, searchQuery]);
|
||||||
|
|
||||||
|
// Effect to initialize showLogs
|
||||||
|
useEffect(() => {
|
||||||
|
logStore.showLogs.set(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Add some initial logs for testing
|
// Add some initial logs for testing
|
||||||
logStore.logSystem('System started', { version: '1.0.0' });
|
logStore.logSystem('System started', { version: '1.0.0' });
|
||||||
@ -18,6 +38,14 @@ export default function EventLogsTab() {
|
|||||||
logStore.logError('Failed to connect to provider', new Error('Connection timeout'), { provider: 'OpenAI' });
|
logStore.logError('Failed to connect to provider', new Error('Connection timeout'), { provider: 'OpenAI' });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const container = document.querySelector('.logs-container');
|
||||||
|
|
||||||
|
if (container && autoScroll) {
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
}
|
||||||
|
}, [filteredLogs, autoScroll]);
|
||||||
|
|
||||||
const handleClearLogs = useCallback(() => {
|
const handleClearLogs = useCallback(() => {
|
||||||
if (confirm('Are you sure you want to clear all logs?')) {
|
if (confirm('Are you sure you want to clear all logs?')) {
|
||||||
logStore.clearLogs();
|
logStore.clearLogs();
|
||||||
@ -54,10 +82,6 @@ export default function EventLogsTab() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const filteredLogs = useMemo(() => {
|
|
||||||
return logStore.getFilteredLogs(logLevel, undefined, searchQuery);
|
|
||||||
}, [logLevel, searchQuery]);
|
|
||||||
|
|
||||||
const getLevelColor = (level: LogEntry['level']) => {
|
const getLevelColor = (level: LogEntry['level']) => {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case 'info':
|
case 'info':
|
||||||
@ -76,15 +100,23 @@ export default function EventLogsTab() {
|
|||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex flex-col space-y-4 mb-4">
|
<div className="flex flex-col space-y-4 mb-4">
|
||||||
<div className="flex justify-between items-center">
|
{/* Title and Toggles Row */}
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||||
<h3 className="text-lg font-medium text-bolt-elements-textPrimary">Event Logs</h3>
|
<h3 className="text-lg font-medium text-bolt-elements-textPrimary">Event Logs</h3>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex flex-wrap items-center gap-4">
|
||||||
<span className="text-sm text-bolt-elements-textSecondary">Auto-scroll</span>
|
<div className="flex items-center space-x-2">
|
||||||
<Switch checked={autoScroll} onCheckedChange={setAutoScroll} />
|
<span className="text-sm text-bolt-elements-textSecondary whitespace-nowrap">Show Actions</span>
|
||||||
|
<Switch checked={showLogs} onCheckedChange={(checked) => logStore.showLogs.set(checked)} />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span className="text-sm text-bolt-elements-textSecondary whitespace-nowrap">Auto-scroll</span>
|
||||||
|
<Switch checked={autoScroll} onCheckedChange={setAutoScroll} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
{/* Controls Row */}
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<select
|
<select
|
||||||
value={logLevel}
|
value={logLevel}
|
||||||
onChange={(e) => setLogLevel(e.target.value as LogEntry['level'])}
|
onChange={(e) => setLogLevel(e.target.value as LogEntry['level'])}
|
||||||
@ -95,29 +127,35 @@ export default function EventLogsTab() {
|
|||||||
<option value="error">Error</option>
|
<option value="error">Error</option>
|
||||||
<option value="debug">Debug</option>
|
<option value="debug">Debug</option>
|
||||||
</select>
|
</select>
|
||||||
<input
|
<div className="flex-1 min-w-[200px]">
|
||||||
type="text"
|
<input
|
||||||
placeholder="Search logs..."
|
type="text"
|
||||||
value={searchQuery}
|
placeholder="Search logs..."
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
value={searchQuery}
|
||||||
className="bg-bolt-elements-bg-depth-2 text-bolt-elements-textPrimary rounded-lg px-3 py-1.5 text-sm flex-1"
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
/>
|
className="w-full bg-bolt-elements-bg-depth-2 text-bolt-elements-textPrimary rounded-lg px-3 py-1.5 text-sm"
|
||||||
<button
|
/>
|
||||||
onClick={handleExportLogs}
|
</div>
|
||||||
className="bg-blue-500 text-white rounded-lg px-3 py-1.5 hover:bg-blue-600 transition-colors duration-200 text-sm whitespace-nowrap"
|
{showLogs && (
|
||||||
>
|
<div className="flex items-center gap-2 flex-nowrap">
|
||||||
Export Logs
|
<button
|
||||||
</button>
|
onClick={handleExportLogs}
|
||||||
<button
|
className="bg-blue-500 text-white rounded-lg px-3 py-1.5 hover:bg-blue-600 transition-colors duration-200 text-sm whitespace-nowrap"
|
||||||
onClick={handleClearLogs}
|
>
|
||||||
className="bg-red-500 text-white rounded-lg px-3 py-1.5 hover:bg-red-600 transition-colors duration-200 text-sm whitespace-nowrap"
|
Export Logs
|
||||||
>
|
</button>
|
||||||
Clear Logs
|
<button
|
||||||
</button>
|
onClick={handleClearLogs}
|
||||||
|
className="bg-red-500 text-white rounded-lg px-3 py-1.5 hover:bg-red-600 transition-colors duration-200 text-sm whitespace-nowrap"
|
||||||
|
>
|
||||||
|
Clear Logs
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-bolt-elements-bg-depth-1 rounded-lg p-4 h-[500px] overflow-y-auto">
|
<div className="bg-bolt-elements-bg-depth-1 rounded-lg p-4 h-[calc(100vh-250px)] min-h-[400px] overflow-y-auto logs-container">
|
||||||
{filteredLogs.length === 0 ? (
|
{filteredLogs.length === 0 ? (
|
||||||
<div className="text-center text-bolt-elements-textSecondary py-8">No logs found</div>
|
<div className="text-center text-bolt-elements-textSecondary py-8">No logs found</div>
|
||||||
) : (
|
) : (
|
||||||
@ -126,13 +164,17 @@ export default function EventLogsTab() {
|
|||||||
key={index}
|
key={index}
|
||||||
className="text-sm mb-3 font-mono border-b border-bolt-elements-borderColor pb-2 last:border-0"
|
className="text-sm mb-3 font-mono border-b border-bolt-elements-borderColor pb-2 last:border-0"
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-2 flex-wrap">
|
<div className="flex items-start space-x-2 flex-wrap">
|
||||||
<span className={`font-bold ${getLevelColor(log.level)}`}>[{log.level.toUpperCase()}]</span>
|
<span className={`font-bold ${getLevelColor(log.level)} whitespace-nowrap`}>
|
||||||
<span className="text-bolt-elements-textSecondary">{new Date(log.timestamp).toLocaleString()}</span>
|
[{log.level.toUpperCase()}]
|
||||||
<span className="text-bolt-elements-textPrimary">{log.message}</span>
|
</span>
|
||||||
|
<span className="text-bolt-elements-textSecondary whitespace-nowrap">
|
||||||
|
{new Date(log.timestamp).toLocaleString()}
|
||||||
|
</span>
|
||||||
|
<span className="text-bolt-elements-textPrimary break-all">{log.message}</span>
|
||||||
</div>
|
</div>
|
||||||
{log.details && (
|
{log.details && (
|
||||||
<pre className="mt-2 text-xs text-bolt-elements-textSecondary overflow-x-auto">
|
<pre className="mt-2 text-xs text-bolt-elements-textSecondary overflow-x-auto whitespace-pre-wrap break-all">
|
||||||
{JSON.stringify(log.details, null, 2)}
|
{JSON.stringify(log.details, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
|
@ -51,11 +51,7 @@ export default function ProvidersTab() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<img
|
<img src={`/icons/${provider.name}.svg`} alt={`${provider.name} icon`} className="w-6 h-6 dark:invert" />
|
||||||
src={`/icons/${provider.name.toLowerCase()}.svg`}
|
|
||||||
alt={`${provider.name} icon`}
|
|
||||||
className="w-6 h-6 dark:invert"
|
|
||||||
/>
|
|
||||||
<span className="text-bolt-elements-textPrimary">{provider.name}</span>
|
<span className="text-bolt-elements-textPrimary">{provider.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
|
@ -17,7 +17,7 @@ const MAX_LOGS = 1000; // Maximum number of logs to keep in memory
|
|||||||
|
|
||||||
class LogStore {
|
class LogStore {
|
||||||
private _logs = map<Record<string, LogEntry>>({});
|
private _logs = map<Record<string, LogEntry>>({});
|
||||||
showLogs = atom(false);
|
showLogs = atom(true);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Load saved logs from cookies on initialization
|
// Load saved logs from cookies on initialization
|
||||||
|
Loading…
Reference in New Issue
Block a user