Skip to content

Commit 593604d

Browse files
committed
Show proxy and upstream logs in separate columns in logs UI
1 parent b8f888f commit 593604d

File tree

1 file changed

+183
-70
lines changed

1 file changed

+183
-70
lines changed

proxy/html/logs.html

+183-70
Original file line numberDiff line numberDiff line change
@@ -12,134 +12,247 @@
1212
flex-direction: column;
1313
font-family: "Courier New", Courier, monospace;
1414
}
15-
#log-controls {
16-
margin: 0.5em;
15+
.log-container {
1716
display: flex;
18-
align-items: center;
19-
justify-content: space-between; /* Spaces out elements evenly */
17+
flex: 1;
18+
gap: 0.5em;
19+
margin: 0.5em;
20+
min-height: 0;
2021
}
21-
#log-controls input {
22+
.log-column {
23+
display: flex;
24+
flex-direction: column;
2225
flex: 1;
26+
min-width: 0;
27+
transition: flex 0.3s ease;
28+
}
29+
.log-column.minimized {
30+
flex: 0.1;
31+
max-width: 50px;
32+
border: 1px solid #777;
33+
color: green;
2334
}
24-
#log-controls input:focus {
25-
outline: none; /* Ensures no outline is shown when the input is focused */
35+
.log-controls {
36+
display: grid;
37+
grid-template-columns: 1fr auto;
38+
gap: 0.5em;
39+
margin-bottom: 0.5em;
2640
}
27-
#log-stream {
41+
.log-controls input {
42+
width: 100%;
43+
padding: 4px;
44+
}
45+
.log-controls input:focus {
46+
outline: none;
47+
}
48+
.log-stream {
2849
flex: 1;
29-
margin: 0.5em;
3050
padding: 1em;
3151
background: #f4f4f4;
3252
overflow-y: auto;
33-
white-space: pre-wrap; /* Ensures line wrapping */
34-
word-wrap: break-word; /* Ensures long words wrap */
53+
white-space: pre-wrap;
54+
word-wrap: break-word;
55+
min-height: 0;
3556
}
3657

3758
.regex-error {
3859
background-color: #ff0000 !important;
3960
}
4061

62+
/* Make headers clickable and show pointer cursor */
63+
h2 {
64+
cursor: pointer;
65+
user-select: none;
66+
margin: 0 0 0.5em 0;
67+
padding: 0.5em;
68+
}
69+
70+
h2:hover {
71+
background-color: rgba(0, 0, 0, 0.05);
72+
}
73+
4174
/* Dark mode styles */
4275
@media (prefers-color-scheme: dark) {
4376
body {
4477
background-color: #333;
4578
color: #fff;
4679
}
4780

48-
#log-stream {
81+
.log-stream {
4982
background: #444;
5083
color: #fff;
5184
}
5285

53-
#log-controls input {
86+
.log-controls input {
5487
background: #555;
5588
color: #fff;
5689
border: 1px solid #777;
5790
}
5891

59-
#log-controls button {
92+
.log-controls button {
6093
background: #555;
6194
color: #fff;
6295
border: 1px solid #777;
6396
}
97+
98+
h2:hover {
99+
background-color: rgba(255, 255, 255, 0.1);
100+
}
101+
}
102+
103+
/* Hide content when minimized */
104+
.log-column.minimized .log-controls,
105+
.log-column.minimized .log-stream {
106+
display: none;
107+
}
108+
109+
.log-column.minimized h2 {
110+
writing-mode: vertical-rl;
111+
text-orientation: mixed;
112+
transform: rotate(180deg);
113+
white-space: nowrap;
114+
margin: auto;
64115
}
65116
</style>
66117
</head>
67118
<body>
68-
<pre id="log-stream">Waiting for logs...</pre>
69-
<div id="log-controls">
70-
<input type="text" id="filter-input" placeholder="regex filter">
71-
<button id="clear-button">clear</button>
119+
<div class="log-container">
120+
<div class="log-column">
121+
<h2>Proxy Logs</h2>
122+
<div class="log-controls">
123+
<input type="text" id="proxy-filter-input" placeholder="proxy regex filter">
124+
<button id="proxy-clear-button">clear</button>
125+
</div>
126+
<pre class="log-stream" id="proxy-log-stream">Waiting for proxy logs...</pre>
127+
</div>
128+
<div class="log-column minimized">
129+
<h2>Upstream Logs</h2>
130+
<div class="log-controls">
131+
<input type="text" id="upstream-filter-input" placeholder="upstream regex filter">
132+
<button id="upstream-clear-button">clear</button>
133+
</div>
134+
<pre class="log-stream" id="upstream-log-stream">Waiting for upstream logs...</pre>
135+
</div>
72136
</div>
73137
<script>
74-
const logStream = document.getElementById('log-stream');
75-
const filterInput = document.getElementById('filter-input');
76-
var logData = "";
77-
let regexFilter = null;
78-
79-
function setupEventSource() {
80-
if (typeof(EventSource) !== "undefined") {
81-
const eventSource = new EventSource("/logs/streamSSE");
82-
83-
eventSource.onmessage = function(event) {
84-
logData += event.data;
85-
render()
86-
};
138+
class LogStream {
139+
constructor(streamElement, filterInput, clearButton, endpoint) {
140+
this.streamElement = streamElement;
141+
this.filterInput = filterInput;
142+
this.clearButton = clearButton;
143+
this.endpoint = endpoint;
144+
this.logData = "";
145+
this.regexFilter = null;
146+
this.eventSource = null;
87147

88-
eventSource.onerror = function(err) {
89-
logData = "EventSource failed: " + err.message;
90-
};
91-
} else {
92-
logData = "SSE Not supported by this browser."
148+
this.initialize();
93149
}
94-
}
95150

96-
// poor-ai's react ¯\_(ツ)_/¯
97-
function render() {
98-
if (regexFilter) {
99-
const lines = logData.split('\n');
100-
const filteredLines = lines.filter(line => {
101-
return regexFilter === null || regexFilter.test(line);
151+
initialize() {
152+
this.filterInput.addEventListener('input', () => this.updateFilter());
153+
this.clearButton.addEventListener('click', () => {
154+
this.filterInput.value = "";
155+
this.regexFilter = null;
156+
this.render();
102157
});
158+
this.setupEventSource();
159+
}
103160

104-
if (filteredLines.length > 0) {
105-
logStream.textContent = filteredLines.join('\n') + '\n';
106-
} else {
107-
logStream.textContent = "";
161+
setupEventSource() {
162+
if (typeof(EventSource) === "undefined") {
163+
this.logData = "SSE Not supported by this browser.";
164+
this.render();
165+
return;
108166
}
109-
} else {
110-
logStream.textContent = logData;
167+
168+
const connect = () => {
169+
this.eventSource = new EventSource(this.endpoint);
170+
171+
this.eventSource.onmessage = (event) => {
172+
this.logData += event.data;
173+
this.render();
174+
};
175+
176+
this.eventSource.onerror = (err) => {
177+
// Close the current connection
178+
this.eventSource.close();
179+
180+
this.logData += "\nConnection lost. Retrying in 5 seconds...\n";
181+
this.render();
182+
183+
// Attempt to reconnect after 5 seconds
184+
setTimeout(() => {
185+
this.logData += "Attempting to reconnect...\n";
186+
this.render();
187+
connect();
188+
}, 5000);
189+
};
190+
};
191+
192+
// Initial connection
193+
connect();
111194
}
112195

113-
logStream.scrollTop = logStream.scrollHeight;
114-
}
196+
render() {
197+
let content = this.logData;
198+
199+
if (this.regexFilter) {
200+
const lines = content.split('\n');
201+
const filteredLines = lines.filter(line => this.regexFilter.test(line));
202+
content = filteredLines.length > 0 ? filteredLines.join('\n') + '\n' : "";
203+
}
204+
205+
this.streamElement.textContent = content;
206+
this.streamElement.scrollTop = this.streamElement.scrollHeight;
207+
}
208+
209+
updateFilter() {
210+
const pattern = this.filterInput.value.trim();
211+
this.filterInput.classList.remove('regex-error');
212+
213+
if (!pattern) {
214+
this.regexFilter = null;
215+
this.render();
216+
return;
217+
}
115218

116-
function updateFilter() {
117-
const pattern = filterInput.value.trim();
118-
filterInput.classList.remove('regex-error');
119-
if (pattern) {
120219
try {
121-
regexFilter = new RegExp(pattern);
220+
this.regexFilter = new RegExp(pattern);
122221
} catch (e) {
123222
console.error("Invalid regex pattern:", e);
124-
regexFilter = null;
125-
filterInput.classList.add('regex-error');
126-
return
223+
this.regexFilter = null;
224+
this.filterInput.classList.add('regex-error');
225+
return;
127226
}
128-
} else {
129-
regexFilter = null;
130-
}
131227

132-
render();
228+
this.render();
229+
}
133230
}
134231

135-
filterInput.addEventListener('input', updateFilter);
136-
document.getElementById('clear-button').addEventListener('click', () => {
137-
filterInput.value = "";
138-
regexFilter = null;
139-
render();
232+
// Initialize both log streams
233+
document.addEventListener('DOMContentLoaded', () => {
234+
new LogStream(
235+
document.getElementById('proxy-log-stream'),
236+
document.getElementById('proxy-filter-input'),
237+
document.getElementById('proxy-clear-button'),
238+
"/logs/streamSSE/proxy"
239+
);
240+
241+
new LogStream(
242+
document.getElementById('upstream-log-stream'),
243+
document.getElementById('upstream-filter-input'),
244+
document.getElementById('upstream-clear-button'),
245+
"/logs/streamSSE/upstream"
246+
);
247+
248+
// Initialize clickable headers
249+
document.querySelectorAll('h2').forEach(header => {
250+
header.addEventListener('click', () => {
251+
const column = header.closest('.log-column');
252+
column.classList.toggle('minimized');
253+
});
254+
});
140255
});
141-
setupEventSource();
142-
updateFilter();
143256
</script>
144257
</body>
145258
</html>

0 commit comments

Comments
 (0)