Stijnus
commited on
Commit
·
87057f8
1
Parent(s):
e39f16e
Event logs bug fix
Browse filesminor improvements download logs, auto scroll, clear logs
app/components/settings/event-logs/EventLogsTab.tsx
CHANGED
@@ -3,14 +3,34 @@ import { useSettings } from '~/lib/hooks/useSettings';
|
|
3 |
import { toast } from 'react-toastify';
|
4 |
import { Switch } from '~/components/ui/Switch';
|
5 |
import { logStore, type LogEntry } from '~/lib/stores/logs';
|
|
|
6 |
|
7 |
export default function EventLogsTab() {
|
8 |
const {} = useSettings();
|
|
|
9 |
const [logLevel, setLogLevel] = useState<LogEntry['level']>('info');
|
10 |
const [autoScroll, setAutoScroll] = useState(true);
|
11 |
const [searchQuery, setSearchQuery] = useState('');
|
12 |
const [, forceUpdate] = useState({});
|
13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
useEffect(() => {
|
15 |
// Add some initial logs for testing
|
16 |
logStore.logSystem('System started', { version: '1.0.0' });
|
@@ -18,6 +38,14 @@ export default function EventLogsTab() {
|
|
18 |
logStore.logError('Failed to connect to provider', new Error('Connection timeout'), { provider: 'OpenAI' });
|
19 |
}, []);
|
20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
const handleClearLogs = useCallback(() => {
|
22 |
if (confirm('Are you sure you want to clear all logs?')) {
|
23 |
logStore.clearLogs();
|
@@ -54,10 +82,6 @@ export default function EventLogsTab() {
|
|
54 |
}
|
55 |
}, []);
|
56 |
|
57 |
-
const filteredLogs = useMemo(() => {
|
58 |
-
return logStore.getFilteredLogs(logLevel, undefined, searchQuery);
|
59 |
-
}, [logLevel, searchQuery]);
|
60 |
-
|
61 |
const getLevelColor = (level: LogEntry['level']) => {
|
62 |
switch (level) {
|
63 |
case 'info':
|
@@ -76,15 +100,23 @@ export default function EventLogsTab() {
|
|
76 |
return (
|
77 |
<div className="p-4">
|
78 |
<div className="flex flex-col space-y-4 mb-4">
|
79 |
-
|
|
|
80 |
<h3 className="text-lg font-medium text-bolt-elements-textPrimary">Event Logs</h3>
|
81 |
-
<div className="flex items-center
|
82 |
-
<
|
83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
</div>
|
85 |
</div>
|
86 |
|
87 |
-
|
|
|
88 |
<select
|
89 |
value={logLevel}
|
90 |
onChange={(e) => setLogLevel(e.target.value as LogEntry['level'])}
|
@@ -95,29 +127,35 @@ export default function EventLogsTab() {
|
|
95 |
<option value="error">Error</option>
|
96 |
<option value="debug">Debug</option>
|
97 |
</select>
|
98 |
-
<
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
</div>
|
118 |
</div>
|
119 |
|
120 |
-
<div className="bg-bolt-elements-bg-depth-1 rounded-lg p-4 h-[
|
121 |
{filteredLogs.length === 0 ? (
|
122 |
<div className="text-center text-bolt-elements-textSecondary py-8">No logs found</div>
|
123 |
) : (
|
@@ -126,13 +164,17 @@ export default function EventLogsTab() {
|
|
126 |
key={index}
|
127 |
className="text-sm mb-3 font-mono border-b border-bolt-elements-borderColor pb-2 last:border-0"
|
128 |
>
|
129 |
-
<div className="flex items-
|
130 |
-
<span className={`font-bold ${getLevelColor(log.level)}`}>
|
131 |
-
|
132 |
-
|
|
|
|
|
|
|
|
|
133 |
</div>
|
134 |
{log.details && (
|
135 |
-
<pre className="mt-2 text-xs text-bolt-elements-textSecondary overflow-x-auto">
|
136 |
{JSON.stringify(log.details, null, 2)}
|
137 |
</pre>
|
138 |
)}
|
|
|
3 |
import { toast } from 'react-toastify';
|
4 |
import { Switch } from '~/components/ui/Switch';
|
5 |
import { logStore, type LogEntry } from '~/lib/stores/logs';
|
6 |
+
import { useStore } from '@nanostores/react';
|
7 |
|
8 |
export default function EventLogsTab() {
|
9 |
const {} = useSettings();
|
10 |
+
const showLogs = useStore(logStore.showLogs);
|
11 |
const [logLevel, setLogLevel] = useState<LogEntry['level']>('info');
|
12 |
const [autoScroll, setAutoScroll] = useState(true);
|
13 |
const [searchQuery, setSearchQuery] = useState('');
|
14 |
const [, forceUpdate] = useState({});
|
15 |
|
16 |
+
const filteredLogs = useMemo(() => {
|
17 |
+
const logs = logStore.getLogs();
|
18 |
+
return logs.filter((log) => {
|
19 |
+
const matchesLevel = !logLevel || log.level === logLevel;
|
20 |
+
const matchesSearch =
|
21 |
+
!searchQuery ||
|
22 |
+
log.message.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
23 |
+
JSON.stringify(log.details).toLowerCase().includes(searchQuery.toLowerCase());
|
24 |
+
|
25 |
+
return matchesLevel && matchesSearch;
|
26 |
+
});
|
27 |
+
}, [logLevel, searchQuery]);
|
28 |
+
|
29 |
+
// Effect to initialize showLogs
|
30 |
+
useEffect(() => {
|
31 |
+
logStore.showLogs.set(true);
|
32 |
+
}, []);
|
33 |
+
|
34 |
useEffect(() => {
|
35 |
// Add some initial logs for testing
|
36 |
logStore.logSystem('System started', { version: '1.0.0' });
|
|
|
38 |
logStore.logError('Failed to connect to provider', new Error('Connection timeout'), { provider: 'OpenAI' });
|
39 |
}, []);
|
40 |
|
41 |
+
useEffect(() => {
|
42 |
+
const container = document.querySelector('.logs-container');
|
43 |
+
|
44 |
+
if (container && autoScroll) {
|
45 |
+
container.scrollTop = container.scrollHeight;
|
46 |
+
}
|
47 |
+
}, [filteredLogs, autoScroll]);
|
48 |
+
|
49 |
const handleClearLogs = useCallback(() => {
|
50 |
if (confirm('Are you sure you want to clear all logs?')) {
|
51 |
logStore.clearLogs();
|
|
|
82 |
}
|
83 |
}, []);
|
84 |
|
|
|
|
|
|
|
|
|
85 |
const getLevelColor = (level: LogEntry['level']) => {
|
86 |
switch (level) {
|
87 |
case 'info':
|
|
|
100 |
return (
|
101 |
<div className="p-4">
|
102 |
<div className="flex flex-col space-y-4 mb-4">
|
103 |
+
{/* Title and Toggles Row */}
|
104 |
+
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
105 |
<h3 className="text-lg font-medium text-bolt-elements-textPrimary">Event Logs</h3>
|
106 |
+
<div className="flex flex-wrap items-center gap-4">
|
107 |
+
<div className="flex items-center space-x-2">
|
108 |
+
<span className="text-sm text-bolt-elements-textSecondary whitespace-nowrap">Show Actions</span>
|
109 |
+
<Switch checked={showLogs} onCheckedChange={(checked) => logStore.showLogs.set(checked)} />
|
110 |
+
</div>
|
111 |
+
<div className="flex items-center space-x-2">
|
112 |
+
<span className="text-sm text-bolt-elements-textSecondary whitespace-nowrap">Auto-scroll</span>
|
113 |
+
<Switch checked={autoScroll} onCheckedChange={setAutoScroll} />
|
114 |
+
</div>
|
115 |
</div>
|
116 |
</div>
|
117 |
|
118 |
+
{/* Controls Row */}
|
119 |
+
<div className="flex flex-wrap items-center gap-2">
|
120 |
<select
|
121 |
value={logLevel}
|
122 |
onChange={(e) => setLogLevel(e.target.value as LogEntry['level'])}
|
|
|
127 |
<option value="error">Error</option>
|
128 |
<option value="debug">Debug</option>
|
129 |
</select>
|
130 |
+
<div className="flex-1 min-w-[200px]">
|
131 |
+
<input
|
132 |
+
type="text"
|
133 |
+
placeholder="Search logs..."
|
134 |
+
value={searchQuery}
|
135 |
+
onChange={(e) => setSearchQuery(e.target.value)}
|
136 |
+
className="w-full bg-bolt-elements-bg-depth-2 text-bolt-elements-textPrimary rounded-lg px-3 py-1.5 text-sm"
|
137 |
+
/>
|
138 |
+
</div>
|
139 |
+
{showLogs && (
|
140 |
+
<div className="flex items-center gap-2 flex-nowrap">
|
141 |
+
<button
|
142 |
+
onClick={handleExportLogs}
|
143 |
+
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"
|
144 |
+
>
|
145 |
+
Export Logs
|
146 |
+
</button>
|
147 |
+
<button
|
148 |
+
onClick={handleClearLogs}
|
149 |
+
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"
|
150 |
+
>
|
151 |
+
Clear Logs
|
152 |
+
</button>
|
153 |
+
</div>
|
154 |
+
)}
|
155 |
</div>
|
156 |
</div>
|
157 |
|
158 |
+
<div className="bg-bolt-elements-bg-depth-1 rounded-lg p-4 h-[calc(100vh-250px)] min-h-[400px] overflow-y-auto logs-container">
|
159 |
{filteredLogs.length === 0 ? (
|
160 |
<div className="text-center text-bolt-elements-textSecondary py-8">No logs found</div>
|
161 |
) : (
|
|
|
164 |
key={index}
|
165 |
className="text-sm mb-3 font-mono border-b border-bolt-elements-borderColor pb-2 last:border-0"
|
166 |
>
|
167 |
+
<div className="flex items-start space-x-2 flex-wrap">
|
168 |
+
<span className={`font-bold ${getLevelColor(log.level)} whitespace-nowrap`}>
|
169 |
+
[{log.level.toUpperCase()}]
|
170 |
+
</span>
|
171 |
+
<span className="text-bolt-elements-textSecondary whitespace-nowrap">
|
172 |
+
{new Date(log.timestamp).toLocaleString()}
|
173 |
+
</span>
|
174 |
+
<span className="text-bolt-elements-textPrimary break-all">{log.message}</span>
|
175 |
</div>
|
176 |
{log.details && (
|
177 |
+
<pre className="mt-2 text-xs text-bolt-elements-textSecondary overflow-x-auto whitespace-pre-wrap break-all">
|
178 |
{JSON.stringify(log.details, null, 2)}
|
179 |
</pre>
|
180 |
)}
|
app/components/settings/providers/ProvidersTab.tsx
CHANGED
@@ -51,11 +51,7 @@ export default function ProvidersTab() {
|
|
51 |
>
|
52 |
<div className="flex items-center justify-between mb-2">
|
53 |
<div className="flex items-center gap-2">
|
54 |
-
<img
|
55 |
-
src={`/icons/${provider.name.toLowerCase()}.svg`}
|
56 |
-
alt={`${provider.name} icon`}
|
57 |
-
className="w-6 h-6 dark:invert"
|
58 |
-
/>
|
59 |
<span className="text-bolt-elements-textPrimary">{provider.name}</span>
|
60 |
</div>
|
61 |
<Switch
|
|
|
51 |
>
|
52 |
<div className="flex items-center justify-between mb-2">
|
53 |
<div className="flex items-center gap-2">
|
54 |
+
<img src={`/icons/${provider.name}.svg`} alt={`${provider.name} icon`} className="w-6 h-6 dark:invert" />
|
|
|
|
|
|
|
|
|
55 |
<span className="text-bolt-elements-textPrimary">{provider.name}</span>
|
56 |
</div>
|
57 |
<Switch
|
app/lib/stores/logs.ts
CHANGED
@@ -17,7 +17,7 @@ const MAX_LOGS = 1000; // Maximum number of logs to keep in memory
|
|
17 |
|
18 |
class LogStore {
|
19 |
private _logs = map<Record<string, LogEntry>>({});
|
20 |
-
showLogs = atom(
|
21 |
|
22 |
constructor() {
|
23 |
// Load saved logs from cookies on initialization
|
|
|
17 |
|
18 |
class LogStore {
|
19 |
private _logs = map<Record<string, LogEntry>>({});
|
20 |
+
showLogs = atom(true);
|
21 |
|
22 |
constructor() {
|
23 |
// Load saved logs from cookies on initialization
|