strimertul-website/static/logviewer.html

187 lines
5.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Online log viewer</title>
<style>
@import url("https://rsms.me/inter/inter.css");
@import url("https://iosevka-webfonts.github.io/iosevka/iosevka.css");
:root {
--site-bg: #171717;
--white: #ededed;
background-color: var(--site-bg);
color: var(--white);
font-family: "Inter", "Gill Sans", "Gill Sans MT", "Segoe UI",
sans-serif;
}
html,
body {
margin: 0;
}
main {
display: flex;
}
#starting {
height: 100vh;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
code {
display: inline-block;
font-family: "Iosevka", monospace;
background-color: #222;
border-radius: 3px;
padding: 3px 6px;
}
.hidden {
display: none !important;
}
</style>
</head>
<body>
<main id="app">
<section id="starting">
<h1>Online log viewer</h1>
<p>
Drag a <code>strimertul.log</code> in this window to view it (or click
anywhere to open a file dialog).<br />
The contents will not be uploaded anywhere, this webpage is fully
local.
</p>
</section>
<table id="logs" class="hidden">
<thead>
<tr>
<th>Level</th>
<th>Time</th>
<th>Caller</th>
<th>Message</th>
<th>Data</th>
</tr>
</thead>
<tbody></tbody>
</table>
</main>
<script>
// @ts-check
const mainEl = document.getElementById("app");
const startingEl = document.getElementById("starting");
const tableEl = document.getElementById("logs");
if (!mainEl || !startingEl || !tableEl) {
throw new Error("Missing elements");
}
/**
* Reads a log file and populates a table with the parsed logs.
*
* @param {File} file - The log file to be read.
* @return {void}
*/
function readLogFile(file) {
const reader = new FileReader();
reader.addEventListener("load", (ev) => {
const logs =
ev.target?.result
?.toString()
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0)
.map((line) => {
return makeLog(JSON.parse(line));
}) || [];
tableEl?.querySelector("tbody")?.replaceChildren(...logs);
tableEl?.classList.remove("hidden");
startingEl?.classList.add("hidden");
});
reader.readAsText(file);
}
// Click handler (open file select)
startingEl.addEventListener("click", (ev) => {
// Create temporary file input and click it
const fileSelect = document.createElement("input");
fileSelect.type = "file";
fileSelect.accept = ".log";
fileSelect.addEventListener("change", (ev) => {
if (ev.target instanceof HTMLInputElement && ev.target.files) {
const file = ev.target.files[0];
startingEl.innerHTML = "Loading...";
if (file) {
readLogFile(file);
}
}
});
fileSelect.click();
});
// Drag and drop handler
mainEl.addEventListener("dragover", (ev) => {
ev.preventDefault();
});
mainEl.addEventListener("drop", (ev) => {
ev.preventDefault();
if (ev.dataTransfer?.files) {
const file = ev.dataTransfer.files[0];
startingEl.innerHTML = "Loading...";
if (file) {
readLogFile(file);
}
}
});
/**
* Creates a log entry as a table row element.
*
* @param log - An object representing a log entry with properties level, ts, caller, msg, and data.
*
* @returns A table row element representing the log entry.
*/
function makeLog(log) {
const tr = document.createElement("tr");
const { level, ts, caller, msg, ...data } = log;
tr.appendChild(makeCell(level));
tr.appendChild(makeCell(makeDateElement(new Date(ts * 1000))));
tr.appendChild(makeCell(caller));
tr.appendChild(makeCell(msg));
tr.appendChild(makeCell(JSON.stringify(data)));
return tr;
}
/**
* Creates a new date element with the given date.
*
* @param {Date} date - The date to be used for the new date element.
* @returns {HTMLTimeElement} The newly created date element.
*/
function makeDateElement(date) {
const time = document.createElement("time");
time.setAttribute("datetime", date.toISOString());
time.appendChild(document.createTextNode(date.toLocaleTimeString()));
return time;
}
/**
* Creates a table cell element with the given text as its content.
*
* @param {string | HTMLElement} el - A string or a DOM element to be used as the content of the cell.
* @returns {HTMLTableCellElement} - A table cell element with the given text as its content.
*/
function makeCell(el) {
const td = document.createElement("td");
if (typeof el === "string") {
td.appendChild(document.createTextNode(el));
} else {
td.appendChild(el);
}
return td;
}
</script>
</body>
</html>