<!DOCTYPE html> <html> <head> <title>Journal</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <style type="text/css"> div#divlogs, div#diventry { font-family: monospace; font-size: 8pt; background-color: #ffffff; padding: 1em; margin: 2em 0em; border-radius: 10px 10px 10px 10px; border: 1px solid threedshadow; white-space: nowrap; overflow-x: scroll; } div#diventry { display: none; } div#divlogs { display: block; } body { background-color: #ededed; color: #313739; font: message-box; margin: 5em; } td.timestamp { text-align: right; border-right: 1px dotted lightgrey; padding-right: 5px; } td.process { border-right: 1px dotted lightgrey; padding-left: 5px; padding-right: 5px; } td.message { padding-left: 5px; } td.message > a:link, td.message > a:visited { text-decoration: none; color: #313739; } td.message-error { padding-left: 5px; color: red; font-weight: bold; } td.message-error > a:link, td.message-error > a:visited { text-decoration: none; color: red; } td.message-highlight { padding-left: 5px; font-weight: bold; } td.message-highlight > a:link, td.message-highlight > a:visited { text-decoration: none; color: #313739; } td > a:hover, td > a:active { text-decoration: underline; color: #c13739; } table#tablelogs, table#tableentry { border-collapse: collapse; } td.field { text-align: right; border-right: 1px dotted lightgrey; padding-right: 5px; } td.data { padding-left: 5px; } div#keynav { text-align: center; font-size: 7pt; color: #818789; padding-top: 2em; } .key { font-weight: bold; color: #313739; } form { text-align: center; } input { font-size: 18pt; font-weight: bold; } </style> </head> <body> <!-- TODO: - live display - show red lines for reboots --> <h1 id="title"></h1> <div id="os"></div> <div id="virtualization"></div> <div id="cutoff"></div> <div id="machine"></div> <div id="usage"></div> <div id="showing"></div> <div id="divlogs"><table id="tablelogs"></table></div> <a name="entry"></a> <div id="diventry"><table id="tableentry"></table></div> <form> <input id="head" type="button" value="⇤" onclick="entriesLoadHead();" title="First Page"/> <input id="previous" type="button" value="←" onclick="entriesLoadPrevious();" title="Previous Page"/> <input id="next" type="button" value="→" onclick="entriesLoadNext();" title="Next Page"/> <input id="tail" type="button" value="⇥" onclick="entriesLoadTail();" title="Last Page"/> <input id="more" type="button" value="+" onclick="entriesMore();" title="More Entries"/> <input id="less" type="button" value="-" onclick="entriesLess();" title="Fewer Entries"/> </form> <div id="keynav"> <span class="key">g</span>: First Page <span class="key">←, k, BACKSPACE</span>: Previous Page <span class="key">→, j, SPACE</span>: Next Page <span class="key">G</span>: Last Page <span class="key">+</span>: More entries <span class="key">-</span>: Fewer entries </div> <script type="text/javascript"> var first_cursor = null; var last_cursor = null; function getNEntries() { var n; n = window.localStorage["n_entries"]; if (n == null) return 50; n = parseInt(n); if (n < 10) return 10; if (n > 1000) return 1000; return n; } function showNEntries(n) { var showing = document.getElementById("showing"); showing.innerHTML = "Showing <b>" + n.toString() + "</b> entries."; } function setNEntries(n) { if (n < 10) return 10; if (n > 1000) return 1000; window.localStorage["n_entries"] = n.toString(); showNEntries(n); } function machineLoad() { var request = new XMLHttpRequest(); request.open("GET", "/machine"); request.onreadystatechange = machineOnResult; request.setRequestHeader("Accept", "application/json"); request.send(null); } function formatBytes(u) { if (u >= 1024*1024*1024*1024) return (u/1024/1024/1024/1024).toFixed(1) + " TiB"; else if (u >= 1024*1024*1024) return (u/1024/1024/1024).toFixed(1) + " GiB"; else if (u >= 1024*1024) return (u/1024/1024).toFixed(1) + " MiB"; else if (u >= 1024) return (u/1024).toFixed(1) + " KiB"; else return u.toString() + " B"; } function escapeHTML(s) { return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); } function machineOnResult(event) { if ((event.currentTarget.readyState != 4) || (event.currentTarget.status != 200 && event.currentTarget.status != 0)) return; var d = JSON.parse(event.currentTarget.responseText); var title = document.getElementById("title"); title.innerHTML = 'Journal of ' + escapeHTML(d.hostname); document.title = 'Journal of ' + escapeHTML(d.hostname); var machine = document.getElementById("machine"); machine.innerHTML = 'Machine ID is <b>' + d.machine_id + '</b>, current boot ID is <b>' + d.boot_id + '</b>.'; var cutoff = document.getElementById("cutoff"); var from = new Date(parseInt(d.cutoff_from_realtime) / 1000); var to = new Date(parseInt(d.cutoff_to_realtime) / 1000); cutoff.innerHTML = 'Journal begins at <b>' + from.toLocaleString() + '</b> and ends at <b>' + to.toLocaleString() + '</b>.'; var usage = document.getElementById("usage"); usage.innerHTML = 'Disk usage is <b>' + formatBytes(parseInt(d.usage)) + '</b>.'; var os = document.getElementById("os"); os.innerHTML = 'Operating system is <b>' + escapeHTML(d.os_pretty_name) + '</b>.'; var virtualization = document.getElementById("virtualization"); virtualization.innerHTML = d.virtualization == "bare" ? "Running on <b>bare metal</b>." : "Running on virtualization <b>" + escapeHTML(d.virtualization) + "</b>."; } function entriesLoad(range) { if (range == null) range = window.localStorage["cursor"] + ":0"; if (range == null) range = ""; var request = new XMLHttpRequest(); request.open("GET", "/entries"); request.onreadystatechange = entriesOnResult; request.setRequestHeader("Accept", "application/json"); request.setRequestHeader("Range", "entries=" + range + ":" + getNEntries().toString()); request.send(null); } function entriesLoadNext() { if (last_cursor == null) entriesLoad(""); else entriesLoad(last_cursor + ":1"); } function entriesLoadPrevious() { if (first_cursor == null) entriesLoad(""); else entriesLoad(first_cursor + ":-" + getNEntries().toString()); } function entriesLoadHead() { entriesLoad(""); } function entriesLoadTail() { entriesLoad(":-" + getNEntries().toString()); } function entriesOnResult(event) { if ((event.currentTarget.readyState != 4) || (event.currentTarget.status != 200 && event.currentTarget.status != 0)) return; var logs = document.getElementById("tablelogs"); var lc = null; var fc = null; var i; var l = event.currentTarget.responseText.split('\n'); if (l.length <= 1) { logs.innerHTML = '<tbody><tr><td colspan="3"><i>No further entries...</i></td></tr></tbody>'; return; } var buf = ''; for (i in l) { if (l[i] == '') continue; var d = JSON.parse(l[i]); if (d.MESSAGE == undefined || d.__CURSOR == undefined) continue; if (fc == null) fc = d.__CURSOR; lc = d.__CURSOR; var priority; if (d.PRIORITY != undefined) priority = parseInt(d.PRIORITY); else priority = 6; if (priority <= 3) clazz = "message-error"; else if (priority <= 5) clazz = "message-highlight"; else clazz = "message"; buf += '<tr><td class="timestamp">'; if (d.__REALTIME_TIMESTAMP != undefined) { var timestamp = new Date(parseInt(d.__REALTIME_TIMESTAMP) / 1000); buf += timestamp.toLocaleString(); } buf += '</td><td class="process">'; if (d.SYSLOG_IDENTIFIER != undefined) buf += escapeHTML(d.SYSLOG_IDENTIFIER); else if (d._COMM != undefined) buf += escapeHTML(d._COMM); if (d._PID != undefined) buf += "[" + escapeHTML(d._PID) + "]"; else if (d.SYSLOG_PID != undefined) buf += "[" + escapeHTML(d.SYSLOG_PID) + "]"; buf += '</td><td class="' + clazz + '"><a href="#entry" onclick="onMessageClick(\'' + d.__CURSOR + '\');">'; if (d.MESSAGE == null) buf += "[blob data]"; else if (d.MESSAGE instanceof Array) buf += "[" + formatBytes(d.MESSAGE.length) + " blob data]"; else buf += escapeHTML(d.MESSAGE); buf += '</a></td></tr>'; } logs.innerHTML = '<tbody>' + buf + '</tbody>'; if (fc != null) { first_cursor = fc; window.localStorage["cursor"] = fc; } if (lc != null) last_cursor = lc; } function entriesMore() { setNEntries(getNEntries() + 10); entriesLoad(first_cursor); } function entriesLess() { setNEntries(getNEntries() - 10); entriesLoad(first_cursor); } function onResultMessageClick(event) { if ((event.currentTarget.readyState != 4) || (event.currentTarget.status != 200 && event.currentTarget.status != 0)) return; var d = JSON.parse(event.currentTarget.responseText); document.getElementById("diventry").style.display = "block"; entry = document.getElementById("tableentry"); var buf = ""; for (var key in d){ var data = d[key]; if (data == null) data = "[blob data]"; else if (data instanceof Array) data = "[" + formatBytes(data.length) + " blob data]"; else data = escapeHTML(data); buf += '<tr><td class="field">' + key + '</td><td class="data">' + data + '</td></tr>'; } entry.innerHTML = '<tbody>' + buf + '</tbody>'; } function onMessageClick(t) { var request = new XMLHttpRequest(); request.open("GET", "/entries?discrete"); request.onreadystatechange = onResultMessageClick; request.setRequestHeader("Accept", "application/json"); request.setRequestHeader("Range", "entries=" + t + ":0:1"); request.send(null); } function onKeyUp(event) { switch (event.keyCode) { case 8: case 37: case 75: entriesLoadPrevious(); break; case 32: case 39: case 74: entriesLoadNext(); break; case 71: if (event.shiftKey) entriesLoadTail(); else entriesLoadHead(); break; case 171: entriesMore(); break; case 173: entriesLess(); break; } } function onMouseWheel(event) { if (event.detail < 0 || event.wheelDelta > 0) entriesLoadPrevious(); else entriesLoadNext(); } machineLoad(); entriesLoad(null); showNEntries(getNEntries()); document.onkeyup = onKeyUp; logs = document.getElementById("divlogs"); logs.addEventListener("mousewheel", onMouseWheel, false); logs.addEventListener("DOMMouseScroll", onMouseWheel, false); </script> </body> </html>