mirror of https://github.com/jetkvm/kvm.git
319 lines
9.0 KiB
HTML
319 lines
9.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Server Sent Event</title>
|
|
<style>
|
|
.main-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
|
|
font-family: 'Hack', monospace;
|
|
font-size: 12px;
|
|
}
|
|
|
|
#loading {
|
|
font-style: italic;
|
|
}
|
|
|
|
.log-entry {
|
|
font-size: 12px;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.log-entry > span {
|
|
min-width: 0;
|
|
overflow-wrap: break-word;
|
|
word-break: break-word;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.log-entry > span:last-child {
|
|
margin-right: 0;
|
|
}
|
|
|
|
.log-entry.log-entry-trace .log-level {
|
|
color: blue;
|
|
}
|
|
|
|
.log-entry.log-entry-debug .log-level {
|
|
color: gray;
|
|
}
|
|
|
|
.log-entry.log-entry-info .log-level {
|
|
color: green;
|
|
}
|
|
|
|
.log-entry.log-entry-warn .log-level {
|
|
color: yellow;
|
|
}
|
|
|
|
.log-entry.log-entry-error .log-level,
|
|
.log-entry.log-entry-fatal .log-level,
|
|
.log-entry.log-entry-panic .log-level {
|
|
color: red;
|
|
}
|
|
|
|
.log-entry.log-entry-info .log-message,
|
|
.log-entry.log-entry-warn .log-message,
|
|
.log-entry.log-entry-error .log-message,
|
|
.log-entry.log-entry-fatal .log-message,
|
|
.log-entry.log-entry-panic .log-message {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.log-timestamp {
|
|
color: #666;
|
|
min-width: 150px;
|
|
}
|
|
|
|
.log-level {
|
|
font-size: 12px;
|
|
min-width: 50px;
|
|
}
|
|
|
|
.log-scope {
|
|
font-size: 12px;
|
|
min-width: 40px;
|
|
}
|
|
|
|
.log-component {
|
|
font-size: 12px;
|
|
min-width: 80px;
|
|
}
|
|
|
|
.log-message {
|
|
font-size: 12px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
max-width: 500px;
|
|
}
|
|
|
|
.log-extras {
|
|
color: #000;
|
|
}
|
|
.log-extras .log-extras-header {
|
|
font-weight: bold;
|
|
color:cornflowerblue;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="main-container">
|
|
<div id="header">
|
|
<span id="loading">
|
|
Connecting to log stream...
|
|
</span>
|
|
|
|
<span id="stats">
|
|
|
|
</span>
|
|
</div>
|
|
<div id="event-data">
|
|
|
|
</div>
|
|
</div>
|
|
</body>
|
|
|
|
<script>
|
|
class LogStream {
|
|
constructor(url, eventDataElement, loadingElement, statsElement) {
|
|
this.url = url;
|
|
this.eventDataElement = eventDataElement;
|
|
this.loadingElement = loadingElement;
|
|
this.statsElement = statsElement;
|
|
this.stream = null;
|
|
this.reconnectAttempts = 0;
|
|
this.maxReconnectAttempts = 10;
|
|
this.reconnectDelay = 1000; // Start with 1 second
|
|
this.maxReconnectDelay = 30000; // Max 30 seconds
|
|
this.isConnecting = false;
|
|
|
|
this.totalMessages = 0;
|
|
|
|
this.connect();
|
|
}
|
|
|
|
connect() {
|
|
if (this.isConnecting) return;
|
|
this.isConnecting = true;
|
|
|
|
this.loadingElement.innerText = "Connecting to log stream...";
|
|
|
|
this.stream = new EventSource(this.url);
|
|
|
|
this.stream.onopen = () => {
|
|
this.isConnecting = false;
|
|
this.reconnectAttempts = 0;
|
|
this.reconnectDelay = 1000;
|
|
this.loadingElement.innerText = "Log stream connected.";
|
|
|
|
|
|
this.totalMessages = 0;
|
|
this.totalBytes = 0;
|
|
};
|
|
|
|
this.stream.onmessage = (event) => {
|
|
this.totalBytes += event.data.length;
|
|
this.totalMessages++;
|
|
|
|
const data = JSON.parse(event.data);
|
|
this.addLogEntry(data);
|
|
this.updateStats();
|
|
};
|
|
|
|
this.stream.onerror = () => {
|
|
this.isConnecting = false;
|
|
this.loadingElement.innerText = "Log stream disconnected.";
|
|
this.stream.close();
|
|
this.handleReconnect();
|
|
};
|
|
}
|
|
|
|
updateStats() {
|
|
this.statsElement.innerHTML = `Messages: <strong>${this.totalMessages}</strong>, Bytes: <strong>${this.totalBytes}</strong> `;
|
|
}
|
|
|
|
handleReconnect() {
|
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
this.loadingElement.innerText = "Failed to reconnect after multiple attempts";
|
|
return;
|
|
}
|
|
|
|
this.reconnectAttempts++;
|
|
this.reconnectDelay = Math.min(this.reconnectDelay * 1, this.maxReconnectDelay);
|
|
|
|
this.loadingElement.innerText = `Reconnecting in ${this.reconnectDelay/1000} seconds... (Attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`;
|
|
|
|
setTimeout(() => {
|
|
this.connect();
|
|
}, this.reconnectDelay);
|
|
}
|
|
|
|
addLogEntry(data) {
|
|
const el = document.createElement("div");
|
|
el.className = "log-entry log-entry-" + data.level;
|
|
|
|
const timestamp = document.createElement("span");
|
|
timestamp.className = "log-timestamp";
|
|
timestamp.innerText = data.time;
|
|
el.appendChild(timestamp);
|
|
|
|
const level = document.createElement("span");
|
|
level.className = "log-level";
|
|
level.innerText = this.shortLogLevel(data.level);
|
|
el.appendChild(level);
|
|
|
|
const scope = document.createElement("span");
|
|
scope.className = "log-scope";
|
|
scope.innerText = data.scope;
|
|
el.appendChild(scope);
|
|
|
|
const component = document.createElement("span");
|
|
component.className = "log-component";
|
|
component.innerText = data.component;
|
|
el.appendChild(component);
|
|
|
|
const message = document.createElement("span");
|
|
message.className = "log-message";
|
|
message.innerText = data.message;
|
|
el.appendChild(message);
|
|
|
|
this.addLogExtras(el, data);
|
|
|
|
this.eventDataElement.appendChild(el);
|
|
|
|
window.scrollTo(0, document.body.scrollHeight);
|
|
}
|
|
|
|
shortLogLevel(level) {
|
|
switch (level) {
|
|
case "trace":
|
|
return "TRC";
|
|
case "debug":
|
|
return "DBG";
|
|
case "info":
|
|
return "INF";
|
|
case "warn":
|
|
return "WRN";
|
|
case "error":
|
|
return "ERR";
|
|
case "fatal":
|
|
return "FTL";
|
|
case "panic":
|
|
return "PNC";
|
|
default:
|
|
return level;
|
|
}
|
|
}
|
|
|
|
addLogExtras(el, data) {
|
|
const excludeKeys = [
|
|
"timestamp",
|
|
"time",
|
|
"level",
|
|
"scope",
|
|
"component",
|
|
"message",
|
|
];
|
|
|
|
const extras = {};
|
|
for (const key in data) {
|
|
if (excludeKeys.includes(key)) {
|
|
continue;
|
|
}
|
|
extras[key] = data[key];
|
|
}
|
|
|
|
for (const key in extras) {
|
|
const extra = document.createElement("span");
|
|
extra.className = "log-extras log-extras-" + key;
|
|
|
|
const extraKey = document.createElement("span");
|
|
extraKey.className = "log-extras-header";
|
|
extraKey.innerText = key + '=';
|
|
extra.appendChild(extraKey);
|
|
|
|
const extraValue = document.createElement("span");
|
|
extraValue.className = "log-extras-value";
|
|
|
|
let value = extras[key];
|
|
if (typeof value === 'object') {
|
|
value = JSON.stringify(value);
|
|
}
|
|
extraValue.innerText = value;
|
|
extra.appendChild(extraValue);
|
|
|
|
el.appendChild(extra);
|
|
}
|
|
}
|
|
|
|
disconnect() {
|
|
if (this.stream) {
|
|
this.stream.close();
|
|
this.stream = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize the log stream when the page loads
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const logStream = new LogStream(
|
|
"/developer/log-stream",
|
|
document.getElementById("event-data"),
|
|
document.getElementById("loading"),
|
|
document.getElementById("stats"),
|
|
);
|
|
|
|
// Clean up when the page is unloaded
|
|
window.addEventListener('beforeunload', () => {
|
|
logStream.disconnect();
|
|
});
|
|
});
|
|
</script>
|
|
|
|
</html> |