]>
Commit | Line | Data |
---|---|---|
bf5542f8 | 1 | <!DOCTYPE html> |
30e31503 | 2 | <!-- SPDX-License-Identifier: LGPL-2.1-or-later --> |
bf5542f8 LP |
3 | <html> |
4 | <head> | |
5 | <title>Journal</title> | |
6 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> | |
7 | <style type="text/css"> | |
c6511e85 | 8 | div#divlogs, div#diventry { |
bf5542f8 | 9 | font-family: monospace; |
1cd8a002 | 10 | font-size: 7pt; |
bf5542f8 LP |
11 | background-color: #ffffff; |
12 | padding: 1em; | |
13 | margin: 2em 0em; | |
14 | border-radius: 10px 10px 10px 10px; | |
15 | border: 1px solid threedshadow; | |
16 | white-space: nowrap; | |
17 | overflow-x: scroll; | |
18 | } | |
c6511e85 LP |
19 | div#diventry { |
20 | display: none; | |
21 | } | |
22 | div#divlogs { | |
23 | display: block; | |
24 | } | |
bf5542f8 LP |
25 | body { |
26 | background-color: #ededed; | |
27 | color: #313739; | |
28 | font: message-box; | |
1cd8a002 | 29 | margin: 3em; |
bf5542f8 | 30 | } |
1dac8b79 LP |
31 | td.timestamp { |
32 | text-align: right; | |
33 | border-right: 1px dotted lightgrey; | |
34 | padding-right: 5px; | |
35 | } | |
36 | td.process { | |
37 | border-right: 1px dotted lightgrey; | |
38 | padding-left: 5px; | |
39 | padding-right: 5px; | |
40 | } | |
41 | td.message { | |
42 | padding-left: 5px; | |
43 | } | |
c6511e85 LP |
44 | td.message > a:link, td.message > a:visited { |
45 | text-decoration: none; | |
46 | color: #313739; | |
47 | } | |
1dac8b79 LP |
48 | td.message-error { |
49 | padding-left: 5px; | |
bf5542f8 LP |
50 | color: red; |
51 | font-weight: bold; | |
52 | } | |
c6511e85 LP |
53 | td.message-error > a:link, td.message-error > a:visited { |
54 | text-decoration: none; | |
55 | color: red; | |
56 | } | |
1dac8b79 LP |
57 | td.message-highlight { |
58 | padding-left: 5px; | |
bf5542f8 LP |
59 | font-weight: bold; |
60 | } | |
c6511e85 LP |
61 | td.message-highlight > a:link, td.message-highlight > a:visited { |
62 | text-decoration: none; | |
63 | color: #313739; | |
64 | } | |
65 | td > a:hover, td > a:active { | |
66 | text-decoration: underline; | |
67 | color: #c13739; | |
68 | } | |
69 | table#tablelogs, table#tableentry { | |
70 | border-collapse: collapse; | |
71 | } | |
72 | td.field { | |
73 | text-align: right; | |
74 | border-right: 1px dotted lightgrey; | |
75 | padding-right: 5px; | |
76 | } | |
77 | td.data { | |
78 | padding-left: 5px; | |
1dac8b79 | 79 | } |
6d5f2f58 LP |
80 | div#keynav { |
81 | text-align: center; | |
82 | font-size: 7pt; | |
83 | color: #818789; | |
74bee4e3 | 84 | padding-top: 2em; |
6d5f2f58 | 85 | } |
1cd8a002 | 86 | span.key { |
6d5f2f58 LP |
87 | font-weight: bold; |
88 | color: #313739; | |
89 | } | |
1cd8a002 | 90 | div#buttonnav { |
74bee4e3 LP |
91 | text-align: center; |
92 | } | |
1cd8a002 | 93 | button { |
33162605 | 94 | font-size: 18pt; |
74bee4e3 | 95 | font-weight: bold; |
1cd8a002 LP |
96 | width: 2em; |
97 | height: 2em; | |
98 | } | |
99 | div#filternav { | |
100 | text-align: center; | |
101 | } | |
102 | select { | |
103 | width: 50em; | |
74bee4e3 | 104 | } |
bf5542f8 LP |
105 | </style> |
106 | </head> | |
107 | ||
108 | <body> | |
109 | <!-- TODO: | |
6c69cd86 | 110 | - live display |
6c69cd86 | 111 | - show red lines for reboots --> |
bf5542f8 LP |
112 | |
113 | <h1 id="title"></h1> | |
114 | ||
115 | <div id="os"></div> | |
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> | |
121 | ||
1cd8a002 LP |
122 | <div id="filternav"> |
123 | <select id="filter" onchange="onFilterChange(this);" onfocus="onFilterFocus(this);"> | |
124 | <option>No filter</option> | |
125 | </select> | |
126 | | |
127 | <input id="boot" type="checkbox" onchange="onBootChange(this);">Only current boot</input> | |
128 | </div> | |
129 | ||
1dac8b79 | 130 | <div id="divlogs"><table id="tablelogs"></table></div> |
c6511e85 LP |
131 | <a name="entry"></a> |
132 | <div id="diventry"><table id="tableentry"></table></div> | |
bf5542f8 | 133 | |
1cd8a002 LP |
134 | <div id="buttonnav"> |
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> | |
bf5542f8 | 139 | |
1cd8a002 LP |
140 | <button id="more" type="button" onclick="entriesMore();" title="More Entries"/>+</button> |
141 | <button id="less" type="button" onclick="entriesLess();" title="Fewer Entries"/>-</button> | |
142 | </div> | |
bf5542f8 | 143 | |
6d5f2f58 | 144 | <div id="keynav"> |
6d5f2f58 | 145 | <span class="key">g</span>: First Page |
74bee4e3 LP |
146 | <span class="key">←, k, BACKSPACE</span>: Previous Page |
147 | <span class="key">→, j, SPACE</span>: Next Page | |
6d5f2f58 LP |
148 | <span class="key">G</span>: Last Page |
149 | <span class="key">+</span>: More entries | |
150 | <span class="key">-</span>: Fewer entries | |
151 | </div> | |
152 | ||
bf5542f8 LP |
153 | <script type="text/javascript"> |
154 | var first_cursor = null; | |
155 | var last_cursor = null; | |
156 | ||
bf5542f8 LP |
157 | function getNEntries() { |
158 | var n; | |
1cd8a002 | 159 | n = localStorage["n_entries"]; |
bf5542f8 LP |
160 | if (n == null) |
161 | return 50; | |
04909b0f LP |
162 | n = parseInt(n); |
163 | if (n < 10) | |
164 | return 10; | |
165 | if (n > 1000) | |
166 | return 1000; | |
167 | return n; | |
bf5542f8 LP |
168 | } |
169 | ||
170 | function showNEntries(n) { | |
171 | var showing = document.getElementById("showing"); | |
172 | showing.innerHTML = "Showing <b>" + n.toString() + "</b> entries."; | |
173 | } | |
174 | ||
175 | function setNEntries(n) { | |
33162605 LP |
176 | if (n < 10) |
177 | return 10; | |
178 | if (n > 1000) | |
179 | return 1000; | |
1cd8a002 | 180 | localStorage["n_entries"] = n.toString(); |
bf5542f8 LP |
181 | showNEntries(n); |
182 | } | |
183 | ||
184 | function machineLoad() { | |
185 | var request = new XMLHttpRequest(); | |
1faba68f | 186 | request.open("GET", "machine"); |
bf5542f8 LP |
187 | request.onreadystatechange = machineOnResult; |
188 | request.setRequestHeader("Accept", "application/json"); | |
189 | request.send(null); | |
190 | } | |
191 | ||
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"; | |
199 | else if (u >= 1024) | |
200 | return (u/1024).toFixed(1) + " KiB"; | |
201 | else | |
202 | return u.toString() + " B"; | |
203 | } | |
204 | ||
522795e0 MM |
205 | function escapeHTML(s) { |
206 | return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); | |
207 | } | |
208 | ||
bf5542f8 LP |
209 | function machineOnResult(event) { |
210 | if ((event.currentTarget.readyState != 4) || | |
211 | (event.currentTarget.status != 200 && event.currentTarget.status != 0)) | |
212 | return; | |
213 | ||
214 | var d = JSON.parse(event.currentTarget.responseText); | |
215 | ||
216 | var title = document.getElementById("title"); | |
6c69cd86 LP |
217 | title.innerHTML = 'Journal of ' + escapeHTML(d.hostname); |
218 | document.title = 'Journal of ' + escapeHTML(d.hostname); | |
bf5542f8 LP |
219 | |
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>.'; | |
222 | ||
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>.'; | |
227 | ||
228 | var usage = document.getElementById("usage"); | |
229 | usage.innerHTML = 'Disk usage is <b>' + formatBytes(parseInt(d.usage)) + '</b>.'; | |
230 | ||
231 | var os = document.getElementById("os"); | |
6c69cd86 | 232 | os.innerHTML = 'Operating system is <b>' + escapeHTML(d.os_pretty_name) + '</b>.'; |
bf5542f8 LP |
233 | |
234 | var virtualization = document.getElementById("virtualization"); | |
6c69cd86 | 235 | virtualization.innerHTML = d.virtualization == "bare" ? "Running on <b>bare metal</b>." : "Running on virtualization <b>" + escapeHTML(d.virtualization) + "</b>."; |
bf5542f8 LP |
236 | } |
237 | ||
238 | function entriesLoad(range) { | |
04909b0f | 239 | |
944072fe YW |
240 | if (range == null) { |
241 | if (localStorage["cursor"] != null && localStorage["cursor"] != "") | |
242 | range = localStorage["cursor"] + ":0"; | |
243 | else | |
244 | range = ""; | |
245 | } | |
04909b0f | 246 | |
1faba68f | 247 | var url = "entries"; |
1cd8a002 LP |
248 | |
249 | if (localStorage["filter"] != "" && localStorage["filter"] != null) { | |
250 | url += "?_SYSTEMD_UNIT=" + escape(localStorage["filter"]); | |
251 | ||
252 | if (localStorage["boot"] == "1") | |
253 | url += "&boot"; | |
254 | } else { | |
255 | if (localStorage["boot"] == "1") | |
256 | url += "?boot"; | |
257 | } | |
258 | ||
bf5542f8 | 259 | var request = new XMLHttpRequest(); |
1cd8a002 | 260 | request.open("GET", url); |
bf5542f8 LP |
261 | request.onreadystatechange = entriesOnResult; |
262 | request.setRequestHeader("Accept", "application/json"); | |
263 | request.setRequestHeader("Range", "entries=" + range + ":" + getNEntries().toString()); | |
264 | request.send(null); | |
265 | } | |
266 | ||
267 | function entriesLoadNext() { | |
268 | if (last_cursor == null) | |
269 | entriesLoad(""); | |
270 | else | |
271 | entriesLoad(last_cursor + ":1"); | |
272 | } | |
273 | ||
274 | function entriesLoadPrevious() { | |
275 | if (first_cursor == null) | |
276 | entriesLoad(""); | |
277 | else | |
278 | entriesLoad(first_cursor + ":-" + getNEntries().toString()); | |
279 | } | |
280 | ||
281 | function entriesLoadHead() { | |
282 | entriesLoad(""); | |
283 | } | |
284 | ||
285 | function entriesLoadTail() { | |
286 | entriesLoad(":-" + getNEntries().toString()); | |
287 | } | |
288 | ||
289 | function entriesOnResult(event) { | |
290 | ||
291 | if ((event.currentTarget.readyState != 4) || | |
292 | (event.currentTarget.status != 200 && event.currentTarget.status != 0)) | |
293 | return; | |
294 | ||
1dac8b79 | 295 | var logs = document.getElementById("tablelogs"); |
bf5542f8 LP |
296 | |
297 | var lc = null; | |
298 | var fc = null; | |
299 | ||
1cd8a002 | 300 | var i, l = event.currentTarget.responseText.split('\n'); |
bf5542f8 LP |
301 | |
302 | if (l.length <= 1) { | |
1dac8b79 | 303 | logs.innerHTML = '<tbody><tr><td colspan="3"><i>No further entries...</i></td></tr></tbody>'; |
bf5542f8 LP |
304 | return; |
305 | } | |
306 | ||
1dac8b79 LP |
307 | var buf = ''; |
308 | ||
bf5542f8 | 309 | for (i in l) { |
bf5542f8 LP |
310 | if (l[i] == '') |
311 | continue; | |
312 | ||
313 | var d = JSON.parse(l[i]); | |
314 | if (d.MESSAGE == undefined || d.__CURSOR == undefined) | |
315 | continue; | |
316 | ||
317 | if (fc == null) | |
318 | fc = d.__CURSOR; | |
319 | lc = d.__CURSOR; | |
320 | ||
321 | var priority; | |
322 | if (d.PRIORITY != undefined) | |
323 | priority = parseInt(d.PRIORITY); | |
324 | else | |
325 | priority = 6; | |
326 | ||
c497e449 | 327 | var clazz; |
bf5542f8 | 328 | if (priority <= 3) |
1dac8b79 | 329 | clazz = "message-error"; |
bf5542f8 | 330 | else if (priority <= 5) |
1dac8b79 | 331 | clazz = "message-highlight"; |
bf5542f8 | 332 | else |
1dac8b79 LP |
333 | clazz = "message"; |
334 | ||
335 | buf += '<tr><td class="timestamp">'; | |
336 | ||
337 | if (d.__REALTIME_TIMESTAMP != undefined) { | |
338 | var timestamp = new Date(parseInt(d.__REALTIME_TIMESTAMP) / 1000); | |
339 | buf += timestamp.toLocaleString(); | |
340 | } | |
bf5542f8 | 341 | |
1dac8b79 | 342 | buf += '</td><td class="process">'; |
bf5542f8 LP |
343 | |
344 | if (d.SYSLOG_IDENTIFIER != undefined) | |
6c69cd86 | 345 | buf += escapeHTML(d.SYSLOG_IDENTIFIER); |
bf5542f8 | 346 | else if (d._COMM != undefined) |
6c69cd86 | 347 | buf += escapeHTML(d._COMM); |
bf5542f8 LP |
348 | |
349 | if (d._PID != undefined) | |
6c69cd86 | 350 | buf += "[" + escapeHTML(d._PID) + "]"; |
bf5542f8 | 351 | else if (d.SYSLOG_PID != undefined) |
6c69cd86 | 352 | buf += "[" + escapeHTML(d.SYSLOG_PID) + "]"; |
1dac8b79 | 353 | |
04909b0f | 354 | buf += '</td><td class="' + clazz + '"><a href="#entry" onclick="onMessageClick(\'' + d.__CURSOR + '\');">'; |
bf5542f8 LP |
355 | |
356 | if (d.MESSAGE == null) | |
1dac8b79 | 357 | buf += "[blob data]"; |
bf5542f8 | 358 | else if (d.MESSAGE instanceof Array) |
1dac8b79 | 359 | buf += "[" + formatBytes(d.MESSAGE.length) + " blob data]"; |
bf5542f8 | 360 | else |
522795e0 | 361 | buf += escapeHTML(d.MESSAGE); |
bf5542f8 | 362 | |
c6511e85 | 363 | buf += '</a></td></tr>'; |
bf5542f8 LP |
364 | } |
365 | ||
c6511e85 | 366 | logs.innerHTML = '<tbody>' + buf + '</tbody>'; |
1dac8b79 | 367 | |
04909b0f | 368 | if (fc != null) { |
bf5542f8 | 369 | first_cursor = fc; |
1cd8a002 | 370 | localStorage["cursor"] = fc; |
04909b0f | 371 | } |
bf5542f8 LP |
372 | if (lc != null) |
373 | last_cursor = lc; | |
374 | } | |
375 | ||
376 | function entriesMore() { | |
377 | setNEntries(getNEntries() + 10); | |
c6511e85 | 378 | entriesLoad(first_cursor); |
bf5542f8 LP |
379 | } |
380 | ||
381 | function entriesLess() { | |
382 | setNEntries(getNEntries() - 10); | |
c6511e85 LP |
383 | entriesLoad(first_cursor); |
384 | } | |
385 | ||
386 | function onResultMessageClick(event) { | |
387 | if ((event.currentTarget.readyState != 4) || | |
388 | (event.currentTarget.status != 200 && event.currentTarget.status != 0)) | |
389 | return; | |
390 | ||
391 | var d = JSON.parse(event.currentTarget.responseText); | |
392 | ||
393 | document.getElementById("diventry").style.display = "block"; | |
c497e449 | 394 | var entry = document.getElementById("tableentry"); |
c6511e85 LP |
395 | |
396 | var buf = ""; | |
9ed794a3 | 397 | for (var key in d) { |
6c69cd86 | 398 | var data = d[key]; |
c6511e85 | 399 | |
6c69cd86 LP |
400 | if (data == null) |
401 | data = "[blob data]"; | |
402 | else if (data instanceof Array) | |
403 | data = "[" + formatBytes(data.length) + " blob data]"; | |
404 | else | |
405 | data = escapeHTML(data); | |
406 | ||
407 | buf += '<tr><td class="field">' + key + '</td><td class="data">' + data + '</td></tr>'; | |
408 | } | |
c6511e85 LP |
409 | entry.innerHTML = '<tbody>' + buf + '</tbody>'; |
410 | } | |
411 | ||
412 | function onMessageClick(t) { | |
413 | var request = new XMLHttpRequest(); | |
1faba68f | 414 | request.open("GET", "entries?discrete"); |
c6511e85 LP |
415 | request.onreadystatechange = onResultMessageClick; |
416 | request.setRequestHeader("Accept", "application/json"); | |
417 | request.setRequestHeader("Range", "entries=" + t + ":0:1"); | |
418 | request.send(null); | |
bf5542f8 LP |
419 | } |
420 | ||
6d5f2f58 LP |
421 | function onKeyUp(event) { |
422 | switch (event.keyCode) { | |
423 | case 8: | |
424 | case 37: | |
425 | case 75: | |
426 | entriesLoadPrevious(); | |
427 | break; | |
428 | case 32: | |
429 | case 39: | |
430 | case 74: | |
431 | entriesLoadNext(); | |
432 | break; | |
433 | ||
434 | case 71: | |
435 | if (event.shiftKey) | |
436 | entriesLoadTail(); | |
437 | else | |
438 | entriesLoadHead(); | |
439 | break; | |
440 | case 171: | |
441 | entriesMore(); | |
442 | break; | |
443 | case 173: | |
444 | entriesLess(); | |
445 | break; | |
446 | } | |
447 | } | |
448 | ||
19f8efac KS |
449 | function onMouseWheel(event) { |
450 | if (event.detail < 0 || event.wheelDelta > 0) | |
451 | entriesLoadPrevious(); | |
452 | else | |
453 | entriesLoadNext(); | |
454 | } | |
455 | ||
1cd8a002 LP |
456 | function onResultFilterFocus(event) { |
457 | if ((event.currentTarget.readyState != 4) || | |
458 | (event.currentTarget.status != 200 && event.currentTarget.status != 0)) | |
459 | return; | |
460 | ||
c497e449 | 461 | var f = document.getElementById("filter"); |
1cd8a002 LP |
462 | |
463 | var l = event.currentTarget.responseText.split('\n'); | |
464 | var buf = '<option>No filter</option>'; | |
465 | var j = -1; | |
466 | ||
467 | for (i in l) { | |
468 | ||
469 | if (l[i] == '') | |
470 | continue; | |
471 | ||
472 | var d = JSON.parse(l[i]); | |
473 | if (d._SYSTEMD_UNIT == undefined) | |
474 | continue; | |
475 | ||
476 | buf += '<option value="' + escape(d._SYSTEMD_UNIT) + '">' + escapeHTML(d._SYSTEMD_UNIT) + '</option>'; | |
477 | ||
478 | if (d._SYSTEMD_UNIT == localStorage["filter"]) | |
479 | j = i; | |
480 | } | |
481 | ||
482 | if (j < 0) { | |
483 | if (localStorage["filter"] != null && localStorage["filter"] != "") { | |
484 | buf += '<option value="' + escape(localStorage["filter"]) + '">' + escapeHTML(localStorage["filter"]) + '</option>'; | |
485 | j = i + 1; | |
486 | } else | |
487 | j = 0; | |
488 | } | |
489 | ||
490 | f.innerHTML = buf; | |
491 | f.selectedIndex = j; | |
492 | } | |
493 | ||
494 | function onFilterFocus(w) { | |
495 | var request = new XMLHttpRequest(); | |
1faba68f | 496 | request.open("GET", "fields/_SYSTEMD_UNIT"); |
1cd8a002 LP |
497 | request.onreadystatechange = onResultFilterFocus; |
498 | request.setRequestHeader("Accept", "application/json"); | |
499 | request.send(null); | |
500 | } | |
501 | ||
502 | function onFilterChange(w) { | |
503 | if (w.selectedIndex <= 0) | |
504 | localStorage["filter"] = ""; | |
505 | else | |
506 | localStorage["filter"] = unescape(w.options[w.selectedIndex].value); | |
507 | ||
508 | entriesLoadHead(); | |
509 | } | |
510 | ||
511 | function onBootChange(w) { | |
512 | localStorage["boot"] = w.checked ? "1" : "0"; | |
513 | entriesLoadHead(); | |
514 | } | |
515 | ||
516 | function initFilter() { | |
c497e449 | 517 | var f = document.getElementById("filter"); |
1cd8a002 LP |
518 | |
519 | var buf = '<option>No filter</option>'; | |
520 | ||
521 | var filter = localStorage["filter"]; | |
c497e449 | 522 | var j; |
1cd8a002 LP |
523 | if (filter != null && filter != "") { |
524 | buf += '<option value="' + escape(filter) + '">' + escapeHTML(filter) + '</option>'; | |
525 | j = 1; | |
526 | } else | |
527 | j = 0; | |
528 | ||
529 | f.innerHTML = buf; | |
530 | f.selectedIndex = j; | |
531 | } | |
532 | ||
533 | function installHandlers() { | |
534 | document.onkeyup = onKeyUp; | |
535 | ||
c497e449 | 536 | var logs = document.getElementById("divlogs"); |
1cd8a002 LP |
537 | logs.addEventListener("mousewheel", onMouseWheel, false); |
538 | logs.addEventListener("DOMMouseScroll", onMouseWheel, false); | |
539 | } | |
540 | ||
bf5542f8 | 541 | machineLoad(); |
04909b0f | 542 | entriesLoad(null); |
bf5542f8 | 543 | showNEntries(getNEntries()); |
1cd8a002 LP |
544 | initFilter(); |
545 | installHandlers(); | |
bf5542f8 LP |
546 | </script> |
547 | </body> | |
548 | </html> |