mirror of
https://github.com/systemd/systemd.git
synced 2024-11-08 11:27:32 +03:00
1e4e7b71e1
Directory src/journal has become one of the largest directories, and since systemd-journal-gatewayd, systemd-journal-remote, and forthcoming systemd-journal-upload are all closely related, create a separate directory for them.
545 lines
23 KiB
HTML
545 lines
23 KiB
HTML
<!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: 7pt;
|
|
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: 3em;
|
|
}
|
|
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;
|
|
}
|
|
span.key {
|
|
font-weight: bold;
|
|
color: #313739;
|
|
}
|
|
div#buttonnav {
|
|
text-align: center;
|
|
}
|
|
button {
|
|
font-size: 18pt;
|
|
font-weight: bold;
|
|
width: 2em;
|
|
height: 2em;
|
|
}
|
|
div#filternav {
|
|
text-align: center;
|
|
}
|
|
select {
|
|
width: 50em;
|
|
}
|
|
</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="filternav">
|
|
<select id="filter" onchange="onFilterChange(this);" onfocus="onFilterFocus(this);">
|
|
<option>No filter</option>
|
|
</select>
|
|
|
|
<input id="boot" type="checkbox" onchange="onBootChange(this);">Only current boot</input>
|
|
</div>
|
|
|
|
<div id="divlogs"><table id="tablelogs"></table></div>
|
|
<a name="entry"></a>
|
|
<div id="diventry"><table id="tableentry"></table></div>
|
|
|
|
<div id="buttonnav">
|
|
<button id="head" onclick="entriesLoadHead();" title="First Page">⇤</button>
|
|
<button id="previous" type="button" onclick="entriesLoadPrevious();" title="Previous Page"/>←</button>
|
|
<button id="next" type="button" onclick="entriesLoadNext();" title="Next Page"/>→</button>
|
|
<button id="tail" type="button" onclick="entriesLoadTail();" title="Last Page"/>⇥</button>
|
|
|
|
<button id="more" type="button" onclick="entriesMore();" title="More Entries"/>+</button>
|
|
<button id="less" type="button" onclick="entriesLess();" title="Fewer Entries"/>-</button>
|
|
</div>
|
|
|
|
<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 = 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;
|
|
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 = localStorage["cursor"] + ":0";
|
|
if (range == null)
|
|
range = "";
|
|
|
|
var url = "/entries";
|
|
|
|
if (localStorage["filter"] != "" && localStorage["filter"] != null) {
|
|
url += "?_SYSTEMD_UNIT=" + escape(localStorage["filter"]);
|
|
|
|
if (localStorage["boot"] == "1")
|
|
url += "&boot";
|
|
} else {
|
|
if (localStorage["boot"] == "1")
|
|
url += "?boot";
|
|
}
|
|
|
|
var request = new XMLHttpRequest();
|
|
request.open("GET", url);
|
|
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, 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;
|
|
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();
|
|
}
|
|
|
|
function onResultFilterFocus(event) {
|
|
if ((event.currentTarget.readyState != 4) ||
|
|
(event.currentTarget.status != 200 && event.currentTarget.status != 0))
|
|
return;
|
|
|
|
f = document.getElementById("filter");
|
|
|
|
var l = event.currentTarget.responseText.split('\n');
|
|
var buf = '<option>No filter</option>';
|
|
var j = -1;
|
|
|
|
for (i in l) {
|
|
|
|
if (l[i] == '')
|
|
continue;
|
|
|
|
var d = JSON.parse(l[i]);
|
|
if (d._SYSTEMD_UNIT == undefined)
|
|
continue;
|
|
|
|
buf += '<option value="' + escape(d._SYSTEMD_UNIT) + '">' + escapeHTML(d._SYSTEMD_UNIT) + '</option>';
|
|
|
|
if (d._SYSTEMD_UNIT == localStorage["filter"])
|
|
j = i;
|
|
}
|
|
|
|
if (j < 0) {
|
|
if (localStorage["filter"] != null && localStorage["filter"] != "") {
|
|
buf += '<option value="' + escape(localStorage["filter"]) + '">' + escapeHTML(localStorage["filter"]) + '</option>';
|
|
j = i + 1;
|
|
} else
|
|
j = 0;
|
|
}
|
|
|
|
f.innerHTML = buf;
|
|
f.selectedIndex = j;
|
|
}
|
|
|
|
function onFilterFocus(w) {
|
|
var request = new XMLHttpRequest();
|
|
request.open("GET", "/fields/_SYSTEMD_UNIT");
|
|
request.onreadystatechange = onResultFilterFocus;
|
|
request.setRequestHeader("Accept", "application/json");
|
|
request.send(null);
|
|
}
|
|
|
|
function onFilterChange(w) {
|
|
if (w.selectedIndex <= 0)
|
|
localStorage["filter"] = "";
|
|
else
|
|
localStorage["filter"] = unescape(w.options[w.selectedIndex].value);
|
|
|
|
entriesLoadHead();
|
|
}
|
|
|
|
function onBootChange(w) {
|
|
localStorage["boot"] = w.checked ? "1" : "0";
|
|
entriesLoadHead();
|
|
}
|
|
|
|
function initFilter() {
|
|
f = document.getElementById("filter");
|
|
|
|
var buf = '<option>No filter</option>';
|
|
|
|
var filter = localStorage["filter"];
|
|
if (filter != null && filter != "") {
|
|
buf += '<option value="' + escape(filter) + '">' + escapeHTML(filter) + '</option>';
|
|
j = 1;
|
|
} else
|
|
j = 0;
|
|
|
|
f.innerHTML = buf;
|
|
f.selectedIndex = j;
|
|
}
|
|
|
|
function installHandlers() {
|
|
document.onkeyup = onKeyUp;
|
|
|
|
logs = document.getElementById("divlogs");
|
|
logs.addEventListener("mousewheel", onMouseWheel, false);
|
|
logs.addEventListener("DOMMouseScroll", onMouseWheel, false);
|
|
}
|
|
|
|
machineLoad();
|
|
entriesLoad(null);
|
|
showNEntries(getNEntries());
|
|
initFilter();
|
|
installHandlers();
|
|
</script>
|
|
</body>
|
|
</html>
|