2 <!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
6 <meta http-equiv=
"Content-Type" content=
"text/html; charset=utf-8"/>
7 <style type=
"text/css">
8 div#divlogs, div#diventry {
9 font-family: monospace;
11 background-color: #ffffff;
14 border-radius:
10px
10px
10px
10px;
15 border:
1px solid threedshadow;
26 background-color: #ededed;
33 border-right:
1px dotted lightgrey;
37 border-right:
1px dotted lightgrey;
44 td.message
> a:link, td.message
> a:visited {
45 text-decoration: none;
53 td.message-error
> a:link, td.message-error
> a:visited {
54 text-decoration: none;
57 td.message-highlight {
61 td.message-highlight
> a:link, td.message-highlight
> a:visited {
62 text-decoration: none;
65 td
> a:hover, td
> a:active {
66 text-decoration: underline;
69 table#tablelogs, table#tableentry {
70 border-collapse: collapse;
74 border-right:
1px dotted lightgrey;
111 - show red lines for reboots -->
116 <div id=
"virtualization"></div>
117 <div id=
"cutoff"></div>
118 <div id=
"machine"></div>
119 <div id=
"usage"></div>
120 <div id=
"showing"></div>
123 <select id=
"filter" onchange=
"onFilterChange(this);" onfocus=
"onFilterFocus(this);">
124 <option>No filter
</option>
126
127 <input id=
"boot" type=
"checkbox" onchange=
"onBootChange(this);">Only current boot
</input>
130 <div id=
"divlogs"><table id=
"tablelogs"></table></div>
132 <div id=
"diventry"><table id=
"tableentry"></table></div>
135 <button id=
"head" onclick=
"entriesLoadHead();" title=
"First Page">⇤</button>
136 <button id=
"previous" type=
"button" onclick=
"entriesLoadPrevious();" title=
"Previous Page"/>←</button>
137 <button id=
"next" type=
"button" onclick=
"entriesLoadNext();" title=
"Next Page"/>→</button>
138 <button id=
"tail" type=
"button" onclick=
"entriesLoadTail();" title=
"Last Page"/>⇥</button>
139
140 <button id=
"more" type=
"button" onclick=
"entriesMore();" title=
"More Entries"/>+
</button>
141 <button id=
"less" type=
"button" onclick=
"entriesLess();" title=
"Fewer Entries"/>-
</button>
145 <span class=
"key">g
</span>: First Page
146 <span class=
"key">←, k, BACKSPACE
</span>: Previous Page
147 <span class=
"key">→, j, SPACE
</span>: Next Page
148 <span class=
"key">G
</span>: Last Page
149 <span class=
"key">+
</span>: More entries
150 <span class=
"key">-
</span>: Fewer entries
153 <script type=
"text/javascript">
154 var first_cursor = null;
155 var last_cursor = null;
157 function getNEntries() {
159 n = localStorage[
"n_entries"];
170 function showNEntries(n) {
171 var showing = document.getElementById(
"showing");
172 showing.innerHTML =
"Showing <b>" + n.toString() +
"</b> entries.";
175 function setNEntries(n) {
180 localStorage[
"n_entries"] = n.toString();
184 function machineLoad() {
185 var request = new XMLHttpRequest();
186 request.open(
"GET",
"machine");
187 request.onreadystatechange = machineOnResult;
188 request.setRequestHeader(
"Accept",
"application/json");
192 function formatBytes(u) {
193 if (u
>=
1024*
1024*
1024*
1024)
194 return (u/
1024/
1024/
1024/
1024).toFixed(
1) +
" TiB";
195 else if (u
>=
1024*
1024*
1024)
196 return (u/
1024/
1024/
1024).toFixed(
1) +
" GiB";
197 else if (u
>=
1024*
1024)
198 return (u/
1024/
1024).toFixed(
1) +
" MiB";
200 return (u/
1024).toFixed(
1) +
" KiB";
202 return u.toString() +
" B";
205 function escapeHTML(s) {
206 return s.replace(/&/g,
"&").replace(/
</g,
"<").replace(
/>/g,
">");
209 function machineOnResult(event) {
210 if ((event.currentTarget.readyState !=
4) ||
211 (event.currentTarget.status !=
200 && event.currentTarget.status !=
0))
214 var d = JSON.parse(event.currentTarget.responseText);
216 var title = document.getElementById(
"title");
217 title.innerHTML = 'Journal of ' + escapeHTML(d.hostname);
218 document.title = 'Journal of ' + escapeHTML(d.hostname);
220 var machine = document.getElementById(
"machine");
221 machine.innerHTML = 'Machine ID is
<b>' + d.machine_id + '
</b>, current boot ID is
<b>' + d.boot_id + '
</b>.';
223 var cutoff = document.getElementById(
"cutoff");
224 var from = new Date(parseInt(d.cutoff_from_realtime) /
1000);
225 var to = new Date(parseInt(d.cutoff_to_realtime) /
1000);
226 cutoff.innerHTML = 'Journal begins at
<b>' + from.toLocaleString() + '
</b> and ends at
<b>' + to.toLocaleString() + '
</b>.';
228 var usage = document.getElementById(
"usage");
229 usage.innerHTML = 'Disk usage is
<b>' + formatBytes(parseInt(d.usage)) + '
</b>.';
231 var os = document.getElementById(
"os");
232 os.innerHTML = 'Operating system is
<b>' + escapeHTML(d.os_pretty_name) + '
</b>.';
234 var virtualization = document.getElementById(
"virtualization");
235 virtualization.innerHTML = d.virtualization ==
"bare" ?
"Running on <b>bare metal</b>." :
"Running on virtualization <b>" + escapeHTML(d.virtualization) +
"</b>.";
238 function entriesLoad(range) {
241 if (localStorage[
"cursor"] != null && localStorage[
"cursor"] !=
"")
242 range = localStorage[
"cursor"] +
":0";
249 if (localStorage[
"filter"] !=
"" && localStorage[
"filter"] != null) {
250 url +=
"?_SYSTEMD_UNIT=" + escape(localStorage[
"filter"]);
252 if (localStorage[
"boot"] ==
"1")
255 if (localStorage[
"boot"] ==
"1")
259 var request = new XMLHttpRequest();
260 request.open(
"GET", url);
261 request.onreadystatechange = entriesOnResult;
262 request.setRequestHeader(
"Accept",
"application/json");
263 request.setRequestHeader(
"Range",
"entries=" + range +
":" + getNEntries().toString());
267 function entriesLoadNext() {
268 if (last_cursor == null)
271 entriesLoad(last_cursor +
":1");
274 function entriesLoadPrevious() {
275 if (first_cursor == null)
278 entriesLoad(first_cursor +
":-" + getNEntries().toString());
281 function entriesLoadHead() {
285 function entriesLoadTail() {
286 entriesLoad(
":-" + getNEntries().toString());
289 function entriesOnResult(event) {
291 if ((event.currentTarget.readyState !=
4) ||
292 (event.currentTarget.status !=
200 && event.currentTarget.status !=
0))
295 var logs = document.getElementById(
"tablelogs");
300 var i, l = event.currentTarget.responseText.split('\n');
303 logs.innerHTML = '
<tbody><tr><td colspan=
"3"><i>No further entries...
</i></td></tr></tbody>';
313 var d = JSON.parse(l[i]);
314 if (d.MESSAGE == undefined || d.__CURSOR == undefined)
322 if (d.PRIORITY != undefined)
323 priority = parseInt(d.PRIORITY);
329 clazz =
"message-error";
330 else if (priority <=
5)
331 clazz =
"message-highlight";
335 buf += '
<tr><td class=
"timestamp">';
337 if (d.__REALTIME_TIMESTAMP != undefined) {
338 var timestamp = new Date(parseInt(d.__REALTIME_TIMESTAMP) /
1000);
339 buf += timestamp.toLocaleString();
342 buf += '
</td><td class=
"process">';
344 if (d.SYSLOG_IDENTIFIER != undefined)
345 buf += escapeHTML(d.SYSLOG_IDENTIFIER);
346 else if (d._COMM != undefined)
347 buf += escapeHTML(d._COMM);
349 if (d._PID != undefined)
350 buf +=
"[" + escapeHTML(d._PID) +
"]";
351 else if (d.SYSLOG_PID != undefined)
352 buf +=
"[" + escapeHTML(d.SYSLOG_PID) +
"]";
354 buf += '
</td><td class=
"' + clazz + '"><a href=
"#entry" onclick=
"onMessageClick(\'' + d.__CURSOR + '\');">';
356 if (d.MESSAGE == null)
357 buf +=
"[blob data]";
358 else if (d.MESSAGE instanceof Array)
359 buf +=
"[" + formatBytes(d.MESSAGE.length) +
" blob data]";
361 buf += escapeHTML(d.MESSAGE);
363 buf += '
</a></td></tr>';
366 logs.innerHTML = '
<tbody>' + buf + '
</tbody>';
370 localStorage[
"cursor"] = fc;
376 function entriesMore() {
377 setNEntries(getNEntries() +
10);
378 entriesLoad(first_cursor);
381 function entriesLess() {
382 setNEntries(getNEntries() -
10);
383 entriesLoad(first_cursor);
386 function onResultMessageClick(event) {
387 if ((event.currentTarget.readyState !=
4) ||
388 (event.currentTarget.status !=
200 && event.currentTarget.status !=
0))
391 var d = JSON.parse(event.currentTarget.responseText);
393 document.getElementById(
"diventry").style.display =
"block";
394 var entry = document.getElementById(
"tableentry");
401 data =
"[blob data]";
402 else if (data instanceof Array)
403 data =
"[" + formatBytes(data.length) +
" blob data]";
405 data = escapeHTML(data);
407 buf += '
<tr><td class=
"field">' + key + '
</td><td class=
"data">' + data + '
</td></tr>';
409 entry.innerHTML = '
<tbody>' + buf + '
</tbody>';
412 function onMessageClick(t) {
413 var request = new XMLHttpRequest();
414 request.open(
"GET",
"entries?discrete");
415 request.onreadystatechange = onResultMessageClick;
416 request.setRequestHeader(
"Accept",
"application/json");
417 request.setRequestHeader(
"Range",
"entries=" + t +
":0:1");
421 function onKeyUp(event) {
422 switch (event.keyCode) {
426 entriesLoadPrevious();
449 function onMouseWheel(event) {
450 if (event.detail <
0 || event.wheelDelta
> 0)
451 entriesLoadPrevious();
456 function onResultFilterFocus(event) {
457 if ((event.currentTarget.readyState !=
4) ||
458 (event.currentTarget.status !=
200 && event.currentTarget.status !=
0))
461 var f = document.getElementById(
"filter");
463 var l = event.currentTarget.responseText.split('\n');
464 var buf = '
<option>No filter
</option>';
472 var d = JSON.parse(l[i]);
473 if (d._SYSTEMD_UNIT == undefined)
476 buf += '
<option value=
"' + escape(d._SYSTEMD_UNIT) + '">' + escapeHTML(d._SYSTEMD_UNIT) + '
</option>';
478 if (d._SYSTEMD_UNIT == localStorage[
"filter"])
483 if (localStorage[
"filter"] != null && localStorage[
"filter"] !=
"") {
484 buf += '
<option value=
"' + escape(localStorage["filter
"]) + '">' + escapeHTML(localStorage[
"filter"]) + '
</option>';
494 function onFilterFocus(w) {
495 var request = new XMLHttpRequest();
496 request.open(
"GET",
"fields/_SYSTEMD_UNIT");
497 request.onreadystatechange = onResultFilterFocus;
498 request.setRequestHeader(
"Accept",
"application/json");
502 function onFilterChange(w) {
503 if (w.selectedIndex <=
0)
504 localStorage[
"filter"] =
"";
506 localStorage[
"filter"] = unescape(w.options[w.selectedIndex].value);
511 function onBootChange(w) {
512 localStorage[
"boot"] = w.checked ?
"1" :
"0";
516 function initFilter() {
517 var f = document.getElementById(
"filter");
519 var buf = '
<option>No filter
</option>';
521 var filter = localStorage[
"filter"];
523 if (filter != null && filter !=
"") {
524 buf += '
<option value=
"' + escape(filter) + '">' + escapeHTML(filter) + '
</option>';
533 function installHandlers() {
534 document.onkeyup = onKeyUp;
536 var logs = document.getElementById(
"divlogs");
537 logs.addEventListener(
"mousewheel", onMouseWheel, false);
538 logs.addEventListener(
"DOMMouseScroll", onMouseWheel, false);
543 showNEntries(getNEntries());