5 <meta http-equiv=
"Content-Type" content=
"text/html; charset=utf-8"/>
6 <style type=
"text/css">
7 div#divlogs, div#diventry {
8 font-family: monospace;
10 background-color: #ffffff;
13 border-radius:
10px
10px
10px
10px;
14 border:
1px solid threedshadow;
25 background-color: #ededed;
32 border-right:
1px dotted lightgrey;
36 border-right:
1px dotted lightgrey;
43 td.message
> a:link, td.message
> a:visited {
44 text-decoration: none;
52 td.message-error
> a:link, td.message-error
> a:visited {
53 text-decoration: none;
56 td.message-highlight {
60 td.message-highlight
> a:link, td.message-highlight
> a:visited {
61 text-decoration: none;
64 td
> a:hover, td
> a:active {
65 text-decoration: underline;
68 table#tablelogs, table#tableentry {
69 border-collapse: collapse;
73 border-right:
1px dotted lightgrey;
110 - show red lines for reboots -->
115 <div id=
"virtualization"></div>
116 <div id=
"cutoff"></div>
117 <div id=
"machine"></div>
118 <div id=
"usage"></div>
119 <div id=
"showing"></div>
122 <select id=
"filter" onchange=
"onFilterChange(this);" onfocus=
"onFilterFocus(this);">
123 <option>No filter
</option>
125
126 <input id=
"boot" type=
"checkbox" onchange=
"onBootChange(this);">Only current boot
</input>
129 <div id=
"divlogs"><table id=
"tablelogs"></table></div>
131 <div id=
"diventry"><table id=
"tableentry"></table></div>
134 <button id=
"head" onclick=
"entriesLoadHead();" title=
"First Page">⇤</button>
135 <button id=
"previous" type=
"button" onclick=
"entriesLoadPrevious();" title=
"Previous Page"/>←</button>
136 <button id=
"next" type=
"button" onclick=
"entriesLoadNext();" title=
"Next Page"/>→</button>
137 <button id=
"tail" type=
"button" onclick=
"entriesLoadTail();" title=
"Last Page"/>⇥</button>
138
139 <button id=
"more" type=
"button" onclick=
"entriesMore();" title=
"More Entries"/>+
</button>
140 <button id=
"less" type=
"button" onclick=
"entriesLess();" title=
"Fewer Entries"/>-
</button>
144 <span class=
"key">g
</span>: First Page
145 <span class=
"key">←, k, BACKSPACE
</span>: Previous Page
146 <span class=
"key">→, j, SPACE
</span>: Next Page
147 <span class=
"key">G
</span>: Last Page
148 <span class=
"key">+
</span>: More entries
149 <span class=
"key">-
</span>: Fewer entries
152 <script type=
"text/javascript">
153 var first_cursor = null;
154 var last_cursor = null;
156 function getNEntries() {
158 n = localStorage[
"n_entries"];
169 function showNEntries(n) {
170 var showing = document.getElementById(
"showing");
171 showing.innerHTML =
"Showing <b>" + n.toString() +
"</b> entries.";
174 function setNEntries(n) {
179 localStorage[
"n_entries"] = n.toString();
183 function machineLoad() {
184 var request = new XMLHttpRequest();
185 request.open(
"GET",
"machine");
186 request.onreadystatechange = machineOnResult;
187 request.setRequestHeader(
"Accept",
"application/json");
191 function formatBytes(u) {
192 if (u
>=
1024*
1024*
1024*
1024)
193 return (u/
1024/
1024/
1024/
1024).toFixed(
1) +
" TiB";
194 else if (u
>=
1024*
1024*
1024)
195 return (u/
1024/
1024/
1024).toFixed(
1) +
" GiB";
196 else if (u
>=
1024*
1024)
197 return (u/
1024/
1024).toFixed(
1) +
" MiB";
199 return (u/
1024).toFixed(
1) +
" KiB";
201 return u.toString() +
" B";
204 function escapeHTML(s) {
205 return s.replace(/&/g,
"&").replace(/
</g,
"<").replace(
/>/g,
">");
208 function machineOnResult(event) {
209 if ((event.currentTarget.readyState !=
4) ||
210 (event.currentTarget.status !=
200 && event.currentTarget.status !=
0))
213 var d = JSON.parse(event.currentTarget.responseText);
215 var title = document.getElementById(
"title");
216 title.innerHTML = 'Journal of ' + escapeHTML(d.hostname);
217 document.title = 'Journal of ' + escapeHTML(d.hostname);
219 var machine = document.getElementById(
"machine");
220 machine.innerHTML = 'Machine ID is
<b>' + d.machine_id + '
</b>, current boot ID is
<b>' + d.boot_id + '
</b>.';
222 var cutoff = document.getElementById(
"cutoff");
223 var from = new Date(parseInt(d.cutoff_from_realtime) /
1000);
224 var to = new Date(parseInt(d.cutoff_to_realtime) /
1000);
225 cutoff.innerHTML = 'Journal begins at
<b>' + from.toLocaleString() + '
</b> and ends at
<b>' + to.toLocaleString() + '
</b>.';
227 var usage = document.getElementById(
"usage");
228 usage.innerHTML = 'Disk usage is
<b>' + formatBytes(parseInt(d.usage)) + '
</b>.';
230 var os = document.getElementById(
"os");
231 os.innerHTML = 'Operating system is
<b>' + escapeHTML(d.os_pretty_name) + '
</b>.';
233 var virtualization = document.getElementById(
"virtualization");
234 virtualization.innerHTML = d.virtualization ==
"bare" ?
"Running on <b>bare metal</b>." :
"Running on virtualization <b>" + escapeHTML(d.virtualization) +
"</b>.";
237 function entriesLoad(range) {
240 if (localStorage[
"cursor"] != null && localStorage[
"cursor"] !=
"")
241 range = localStorage[
"cursor"] +
":0";
248 if (localStorage[
"filter"] !=
"" && localStorage[
"filter"] != null) {
249 url +=
"?_SYSTEMD_UNIT=" + escape(localStorage[
"filter"]);
251 if (localStorage[
"boot"] ==
"1")
254 if (localStorage[
"boot"] ==
"1")
258 var request = new XMLHttpRequest();
259 request.open(
"GET", url);
260 request.onreadystatechange = entriesOnResult;
261 request.setRequestHeader(
"Accept",
"application/json");
262 request.setRequestHeader(
"Range",
"entries=" + range +
":" + getNEntries().toString());
266 function entriesLoadNext() {
267 if (last_cursor == null)
270 entriesLoad(last_cursor +
":1");
273 function entriesLoadPrevious() {
274 if (first_cursor == null)
277 entriesLoad(first_cursor +
":-" + getNEntries().toString());
280 function entriesLoadHead() {
284 function entriesLoadTail() {
285 entriesLoad(
":-" + getNEntries().toString());
288 function entriesOnResult(event) {
290 if ((event.currentTarget.readyState !=
4) ||
291 (event.currentTarget.status !=
200 && event.currentTarget.status !=
0))
294 var logs = document.getElementById(
"tablelogs");
299 var i, l = event.currentTarget.responseText.split('\n');
302 logs.innerHTML = '
<tbody><tr><td colspan=
"3"><i>No further entries...
</i></td></tr></tbody>';
312 var d = JSON.parse(l[i]);
313 if (d.MESSAGE == undefined || d.__CURSOR == undefined)
321 if (d.PRIORITY != undefined)
322 priority = parseInt(d.PRIORITY);
328 clazz =
"message-error";
329 else if (priority <=
5)
330 clazz =
"message-highlight";
334 buf += '
<tr><td class=
"timestamp">';
336 if (d.__REALTIME_TIMESTAMP != undefined) {
337 var timestamp = new Date(parseInt(d.__REALTIME_TIMESTAMP) /
1000);
338 buf += timestamp.toLocaleString();
341 buf += '
</td><td class=
"process">';
343 if (d.SYSLOG_IDENTIFIER != undefined)
344 buf += escapeHTML(d.SYSLOG_IDENTIFIER);
345 else if (d._COMM != undefined)
346 buf += escapeHTML(d._COMM);
348 if (d._PID != undefined)
349 buf +=
"[" + escapeHTML(d._PID) +
"]";
350 else if (d.SYSLOG_PID != undefined)
351 buf +=
"[" + escapeHTML(d.SYSLOG_PID) +
"]";
353 buf += '
</td><td class=
"' + clazz + '"><a href=
"#entry" onclick=
"onMessageClick(\'' + d.__CURSOR + '\');">';
355 if (d.MESSAGE == null)
356 buf +=
"[blob data]";
357 else if (d.MESSAGE instanceof Array)
358 buf +=
"[" + formatBytes(d.MESSAGE.length) +
" blob data]";
360 buf += escapeHTML(d.MESSAGE);
362 buf += '
</a></td></tr>';
365 logs.innerHTML = '
<tbody>' + buf + '
</tbody>';
369 localStorage[
"cursor"] = fc;
375 function entriesMore() {
376 setNEntries(getNEntries() +
10);
377 entriesLoad(first_cursor);
380 function entriesLess() {
381 setNEntries(getNEntries() -
10);
382 entriesLoad(first_cursor);
385 function onResultMessageClick(event) {
386 if ((event.currentTarget.readyState !=
4) ||
387 (event.currentTarget.status !=
200 && event.currentTarget.status !=
0))
390 var d = JSON.parse(event.currentTarget.responseText);
392 document.getElementById(
"diventry").style.display =
"block";
393 var entry = document.getElementById(
"tableentry");
400 data =
"[blob data]";
401 else if (data instanceof Array)
402 data =
"[" + formatBytes(data.length) +
" blob data]";
404 data = escapeHTML(data);
406 buf += '
<tr><td class=
"field">' + key + '
</td><td class=
"data">' + data + '
</td></tr>';
408 entry.innerHTML = '
<tbody>' + buf + '
</tbody>';
411 function onMessageClick(t) {
412 var request = new XMLHttpRequest();
413 request.open(
"GET",
"entries?discrete");
414 request.onreadystatechange = onResultMessageClick;
415 request.setRequestHeader(
"Accept",
"application/json");
416 request.setRequestHeader(
"Range",
"entries=" + t +
":0:1");
420 function onKeyUp(event) {
421 switch (event.keyCode) {
425 entriesLoadPrevious();
448 function onMouseWheel(event) {
449 if (event.detail <
0 || event.wheelDelta
> 0)
450 entriesLoadPrevious();
455 function onResultFilterFocus(event) {
456 if ((event.currentTarget.readyState !=
4) ||
457 (event.currentTarget.status !=
200 && event.currentTarget.status !=
0))
460 var f = document.getElementById(
"filter");
462 var l = event.currentTarget.responseText.split('\n');
463 var buf = '
<option>No filter
</option>';
471 var d = JSON.parse(l[i]);
472 if (d._SYSTEMD_UNIT == undefined)
475 buf += '
<option value=
"' + escape(d._SYSTEMD_UNIT) + '">' + escapeHTML(d._SYSTEMD_UNIT) + '
</option>';
477 if (d._SYSTEMD_UNIT == localStorage[
"filter"])
482 if (localStorage[
"filter"] != null && localStorage[
"filter"] !=
"") {
483 buf += '
<option value=
"' + escape(localStorage["filter
"]) + '">' + escapeHTML(localStorage[
"filter"]) + '
</option>';
493 function onFilterFocus(w) {
494 var request = new XMLHttpRequest();
495 request.open(
"GET",
"fields/_SYSTEMD_UNIT");
496 request.onreadystatechange = onResultFilterFocus;
497 request.setRequestHeader(
"Accept",
"application/json");
501 function onFilterChange(w) {
502 if (w.selectedIndex <=
0)
503 localStorage[
"filter"] =
"";
505 localStorage[
"filter"] = unescape(w.options[w.selectedIndex].value);
510 function onBootChange(w) {
511 localStorage[
"boot"] = w.checked ?
"1" :
"0";
515 function initFilter() {
516 var f = document.getElementById(
"filter");
518 var buf = '
<option>No filter
</option>';
520 var filter = localStorage[
"filter"];
522 if (filter != null && filter !=
"") {
523 buf += '
<option value=
"' + escape(filter) + '">' + escapeHTML(filter) + '
</option>';
532 function installHandlers() {
533 document.onkeyup = onKeyUp;
535 var logs = document.getElementById(
"divlogs");
536 logs.addEventListener(
"mousewheel", onMouseWheel, false);
537 logs.addEventListener(
"DOMMouseScroll", onMouseWheel, false);
542 showNEntries(getNEntries());