]>
Commit | Line | Data |
---|---|---|
bf5542f8 LP |
1 | <!DOCTYPE html> |
2 | <html> | |
3 | <head> | |
4 | <title>Journal</title> | |
5 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> | |
6 | <style type="text/css"> | |
c6511e85 | 7 | div#divlogs, div#diventry { |
bf5542f8 LP |
8 | font-family: monospace; |
9 | font-size: 8pt; | |
10 | background-color: #ffffff; | |
11 | padding: 1em; | |
12 | margin: 2em 0em; | |
13 | border-radius: 10px 10px 10px 10px; | |
14 | border: 1px solid threedshadow; | |
15 | white-space: nowrap; | |
16 | overflow-x: scroll; | |
17 | } | |
c6511e85 LP |
18 | div#diventry { |
19 | display: none; | |
20 | } | |
21 | div#divlogs { | |
22 | display: block; | |
23 | } | |
bf5542f8 LP |
24 | body { |
25 | background-color: #ededed; | |
26 | color: #313739; | |
27 | font: message-box; | |
28 | margin: 5em; | |
29 | } | |
1dac8b79 LP |
30 | td.timestamp { |
31 | text-align: right; | |
32 | border-right: 1px dotted lightgrey; | |
33 | padding-right: 5px; | |
34 | } | |
35 | td.process { | |
36 | border-right: 1px dotted lightgrey; | |
37 | padding-left: 5px; | |
38 | padding-right: 5px; | |
39 | } | |
40 | td.message { | |
41 | padding-left: 5px; | |
42 | } | |
c6511e85 LP |
43 | td.message > a:link, td.message > a:visited { |
44 | text-decoration: none; | |
45 | color: #313739; | |
46 | } | |
1dac8b79 LP |
47 | td.message-error { |
48 | padding-left: 5px; | |
bf5542f8 LP |
49 | color: red; |
50 | font-weight: bold; | |
51 | } | |
c6511e85 LP |
52 | td.message-error > a:link, td.message-error > a:visited { |
53 | text-decoration: none; | |
54 | color: red; | |
55 | } | |
1dac8b79 LP |
56 | td.message-highlight { |
57 | padding-left: 5px; | |
bf5542f8 LP |
58 | font-weight: bold; |
59 | } | |
c6511e85 LP |
60 | td.message-highlight > a:link, td.message-highlight > a:visited { |
61 | text-decoration: none; | |
62 | color: #313739; | |
63 | } | |
64 | td > a:hover, td > a:active { | |
65 | text-decoration: underline; | |
66 | color: #c13739; | |
67 | } | |
68 | table#tablelogs, table#tableentry { | |
69 | border-collapse: collapse; | |
70 | } | |
71 | td.field { | |
72 | text-align: right; | |
73 | border-right: 1px dotted lightgrey; | |
74 | padding-right: 5px; | |
75 | } | |
76 | td.data { | |
77 | padding-left: 5px; | |
1dac8b79 | 78 | } |
6d5f2f58 LP |
79 | div#keynav { |
80 | text-align: center; | |
81 | font-size: 7pt; | |
82 | color: #818789; | |
83 | padding-top: 50px; | |
84 | } | |
85 | .key { | |
86 | font-weight: bold; | |
87 | color: #313739; | |
88 | } | |
bf5542f8 LP |
89 | </style> |
90 | </head> | |
91 | ||
92 | <body> | |
93 | <!-- TODO: | |
6c69cd86 | 94 | - live display |
6c69cd86 LP |
95 | - localstorage |
96 | - show red lines for reboots --> | |
bf5542f8 LP |
97 | |
98 | <h1 id="title"></h1> | |
99 | ||
100 | <div id="os"></div> | |
101 | <div id="virtualization"></div> | |
102 | <div id="cutoff"></div> | |
103 | <div id="machine"></div> | |
104 | <div id="usage"></div> | |
105 | <div id="showing"></div> | |
106 | ||
1dac8b79 | 107 | <div id="divlogs"><table id="tablelogs"></table></div> |
c6511e85 LP |
108 | <a name="entry"></a> |
109 | <div id="diventry"><table id="tableentry"></table></div> | |
bf5542f8 LP |
110 | |
111 | <form> | |
112 | <input id="head" type="button" value="|<" onclick="entriesLoadHead();"/> | |
113 | <input id="previous" type="button" value="<<" onclick="entriesLoadPrevious();"/> | |
114 | <input id="next" type="button" value=">>" onclick="entriesLoadNext();"/> | |
115 | <input id="tail" type="button" value=">|" onclick="entriesLoadTail();"/> | |
116 | | |
117 | <input id="more" type="button" value="More" onclick="entriesMore();"/> | |
118 | <input id="less" type="button" value="Less" onclick="entriesLess();"/> | |
119 | </form> | |
120 | ||
6d5f2f58 LP |
121 | <div id="keynav"> |
122 | <span class="key">→, j, SPACE</span>: Next Page | |
123 | <span class="key">←, k, BACKSPACE</span>: Previous Page | |
124 | <span class="key">g</span>: First Page | |
125 | <span class="key">G</span>: Last Page | |
126 | <span class="key">+</span>: More entries | |
127 | <span class="key">-</span>: Fewer entries | |
128 | </div> | |
129 | ||
bf5542f8 LP |
130 | <script type="text/javascript"> |
131 | var first_cursor = null; | |
132 | var last_cursor = null; | |
133 | ||
134 | function setCookie(name, value, msec) { | |
135 | var d = new Date(); | |
136 | d.setMilliseconds(d.getMilliseconds() + msec); | |
137 | var v = escape(value) + "; expires=" + d.toUTCString(); | |
138 | document.cookie = name + "=" + value; | |
139 | } | |
140 | ||
141 | function getCookie(name) { | |
142 | var i, l; | |
143 | l = document.cookie.split(";"); | |
144 | for (i in l) { | |
145 | var x, y, j; | |
146 | j = l[i].indexOf("="); | |
147 | x = l[i].substr(0, j); | |
148 | y = l[i].substr(j+1); | |
149 | if (x == name) | |
150 | return unescape(y); | |
151 | } | |
152 | return null; | |
153 | } | |
154 | ||
155 | function getNEntries() { | |
156 | var n; | |
157 | n = getCookie("n_entries"); | |
158 | if (n == null) | |
159 | return 50; | |
160 | return parseInt(n); | |
161 | } | |
162 | ||
163 | function showNEntries(n) { | |
164 | var showing = document.getElementById("showing"); | |
165 | showing.innerHTML = "Showing <b>" + n.toString() + "</b> entries."; | |
166 | } | |
167 | ||
168 | function setNEntries(n) { | |
169 | if (n < 10) | |
170 | n = 10; | |
171 | else if (n > 1000) | |
172 | n = 1000; | |
173 | ||
174 | setCookie("n_entries", n.toString(), 30*24*60*60*1000); | |
175 | showNEntries(n); | |
176 | } | |
177 | ||
178 | function machineLoad() { | |
179 | var request = new XMLHttpRequest(); | |
180 | request.open("GET", "/machine"); | |
181 | request.onreadystatechange = machineOnResult; | |
182 | request.setRequestHeader("Accept", "application/json"); | |
183 | request.send(null); | |
184 | } | |
185 | ||
186 | function formatBytes(u) { | |
187 | if (u >= 1024*1024*1024*1024) | |
188 | return (u/1024/1024/1024/1024).toFixed(1) + " TiB"; | |
189 | else if (u >= 1024*1024*1024) | |
190 | return (u/1024/1024/1024).toFixed(1) + " GiB"; | |
191 | else if (u >= 1024*1024) | |
192 | return (u/1024/1024).toFixed(1) + " MiB"; | |
193 | else if (u >= 1024) | |
194 | return (u/1024).toFixed(1) + " KiB"; | |
195 | else | |
196 | return u.toString() + " B"; | |
197 | } | |
198 | ||
522795e0 MM |
199 | function escapeHTML(s) { |
200 | return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); | |
201 | } | |
202 | ||
bf5542f8 LP |
203 | function machineOnResult(event) { |
204 | if ((event.currentTarget.readyState != 4) || | |
205 | (event.currentTarget.status != 200 && event.currentTarget.status != 0)) | |
206 | return; | |
207 | ||
208 | var d = JSON.parse(event.currentTarget.responseText); | |
209 | ||
210 | var title = document.getElementById("title"); | |
6c69cd86 LP |
211 | title.innerHTML = 'Journal of ' + escapeHTML(d.hostname); |
212 | document.title = 'Journal of ' + escapeHTML(d.hostname); | |
bf5542f8 LP |
213 | |
214 | var machine = document.getElementById("machine"); | |
215 | machine.innerHTML = 'Machine ID is <b>' + d.machine_id + '</b>, current boot ID is <b>' + d.boot_id + '</b>.'; | |
216 | ||
217 | var cutoff = document.getElementById("cutoff"); | |
218 | var from = new Date(parseInt(d.cutoff_from_realtime) / 1000); | |
219 | var to = new Date(parseInt(d.cutoff_to_realtime) / 1000); | |
220 | cutoff.innerHTML = 'Journal begins at <b>' + from.toLocaleString() + '</b> and ends at <b>' + to.toLocaleString() + '</b>.'; | |
221 | ||
222 | var usage = document.getElementById("usage"); | |
223 | usage.innerHTML = 'Disk usage is <b>' + formatBytes(parseInt(d.usage)) + '</b>.'; | |
224 | ||
225 | var os = document.getElementById("os"); | |
6c69cd86 | 226 | os.innerHTML = 'Operating system is <b>' + escapeHTML(d.os_pretty_name) + '</b>.'; |
bf5542f8 LP |
227 | |
228 | var virtualization = document.getElementById("virtualization"); | |
6c69cd86 | 229 | virtualization.innerHTML = d.virtualization == "bare" ? "Running on <b>bare metal</b>." : "Running on virtualization <b>" + escapeHTML(d.virtualization) + "</b>."; |
bf5542f8 LP |
230 | } |
231 | ||
232 | function entriesLoad(range) { | |
233 | var request = new XMLHttpRequest(); | |
234 | request.open("GET", "/entries"); | |
235 | request.onreadystatechange = entriesOnResult; | |
236 | request.setRequestHeader("Accept", "application/json"); | |
237 | request.setRequestHeader("Range", "entries=" + range + ":" + getNEntries().toString()); | |
238 | request.send(null); | |
239 | } | |
240 | ||
241 | function entriesLoadNext() { | |
242 | if (last_cursor == null) | |
243 | entriesLoad(""); | |
244 | else | |
245 | entriesLoad(last_cursor + ":1"); | |
246 | } | |
247 | ||
248 | function entriesLoadPrevious() { | |
249 | if (first_cursor == null) | |
250 | entriesLoad(""); | |
251 | else | |
252 | entriesLoad(first_cursor + ":-" + getNEntries().toString()); | |
253 | } | |
254 | ||
255 | function entriesLoadHead() { | |
256 | entriesLoad(""); | |
257 | } | |
258 | ||
259 | function entriesLoadTail() { | |
260 | entriesLoad(":-" + getNEntries().toString()); | |
261 | } | |
262 | ||
263 | function entriesOnResult(event) { | |
264 | ||
265 | if ((event.currentTarget.readyState != 4) || | |
266 | (event.currentTarget.status != 200 && event.currentTarget.status != 0)) | |
267 | return; | |
268 | ||
1dac8b79 | 269 | var logs = document.getElementById("tablelogs"); |
bf5542f8 LP |
270 | |
271 | var lc = null; | |
272 | var fc = null; | |
273 | ||
274 | var i; | |
275 | var l = event.currentTarget.responseText.split('\n'); | |
276 | ||
277 | if (l.length <= 1) { | |
1dac8b79 | 278 | logs.innerHTML = '<tbody><tr><td colspan="3"><i>No further entries...</i></td></tr></tbody>'; |
bf5542f8 LP |
279 | return; |
280 | } | |
281 | ||
1dac8b79 LP |
282 | var buf = ''; |
283 | ||
bf5542f8 LP |
284 | for (i in l) { |
285 | ||
286 | if (l[i] == '') | |
287 | continue; | |
288 | ||
289 | var d = JSON.parse(l[i]); | |
290 | if (d.MESSAGE == undefined || d.__CURSOR == undefined) | |
291 | continue; | |
292 | ||
293 | if (fc == null) | |
294 | fc = d.__CURSOR; | |
295 | lc = d.__CURSOR; | |
296 | ||
297 | var priority; | |
298 | if (d.PRIORITY != undefined) | |
299 | priority = parseInt(d.PRIORITY); | |
300 | else | |
301 | priority = 6; | |
302 | ||
303 | if (priority <= 3) | |
1dac8b79 | 304 | clazz = "message-error"; |
bf5542f8 | 305 | else if (priority <= 5) |
1dac8b79 | 306 | clazz = "message-highlight"; |
bf5542f8 | 307 | else |
1dac8b79 LP |
308 | clazz = "message"; |
309 | ||
310 | buf += '<tr><td class="timestamp">'; | |
311 | ||
312 | if (d.__REALTIME_TIMESTAMP != undefined) { | |
313 | var timestamp = new Date(parseInt(d.__REALTIME_TIMESTAMP) / 1000); | |
314 | buf += timestamp.toLocaleString(); | |
315 | } | |
bf5542f8 | 316 | |
1dac8b79 | 317 | buf += '</td><td class="process">'; |
bf5542f8 LP |
318 | |
319 | if (d.SYSLOG_IDENTIFIER != undefined) | |
6c69cd86 | 320 | buf += escapeHTML(d.SYSLOG_IDENTIFIER); |
bf5542f8 | 321 | else if (d._COMM != undefined) |
6c69cd86 | 322 | buf += escapeHTML(d._COMM); |
bf5542f8 LP |
323 | |
324 | if (d._PID != undefined) | |
6c69cd86 | 325 | buf += "[" + escapeHTML(d._PID) + "]"; |
bf5542f8 | 326 | else if (d.SYSLOG_PID != undefined) |
6c69cd86 | 327 | buf += "[" + escapeHTML(d.SYSLOG_PID) + "]"; |
1dac8b79 | 328 | |
c6511e85 | 329 | buf += '</td><td class="' + clazz + '"><a href="#entry" onclick="onMessageClick(\'' + lc + '\');">'; |
bf5542f8 LP |
330 | |
331 | if (d.MESSAGE == null) | |
1dac8b79 | 332 | buf += "[blob data]"; |
bf5542f8 | 333 | else if (d.MESSAGE instanceof Array) |
1dac8b79 | 334 | buf += "[" + formatBytes(d.MESSAGE.length) + " blob data]"; |
bf5542f8 | 335 | else |
522795e0 | 336 | buf += escapeHTML(d.MESSAGE); |
bf5542f8 | 337 | |
c6511e85 | 338 | buf += '</a></td></tr>'; |
bf5542f8 LP |
339 | } |
340 | ||
c6511e85 | 341 | logs.innerHTML = '<tbody>' + buf + '</tbody>'; |
1dac8b79 | 342 | |
bf5542f8 LP |
343 | if (fc != null) |
344 | first_cursor = fc; | |
345 | if (lc != null) | |
346 | last_cursor = lc; | |
347 | } | |
348 | ||
349 | function entriesMore() { | |
350 | setNEntries(getNEntries() + 10); | |
c6511e85 | 351 | entriesLoad(first_cursor); |
bf5542f8 LP |
352 | } |
353 | ||
354 | function entriesLess() { | |
355 | setNEntries(getNEntries() - 10); | |
c6511e85 LP |
356 | entriesLoad(first_cursor); |
357 | } | |
358 | ||
359 | function onResultMessageClick(event) { | |
360 | if ((event.currentTarget.readyState != 4) || | |
361 | (event.currentTarget.status != 200 && event.currentTarget.status != 0)) | |
362 | return; | |
363 | ||
364 | var d = JSON.parse(event.currentTarget.responseText); | |
365 | ||
366 | document.getElementById("diventry").style.display = "block"; | |
c6511e85 LP |
367 | entry = document.getElementById("tableentry"); |
368 | ||
369 | var buf = ""; | |
c6511e85 | 370 | for (var key in d){ |
6c69cd86 | 371 | var data = d[key]; |
c6511e85 | 372 | |
6c69cd86 LP |
373 | if (data == null) |
374 | data = "[blob data]"; | |
375 | else if (data instanceof Array) | |
376 | data = "[" + formatBytes(data.length) + " blob data]"; | |
377 | else | |
378 | data = escapeHTML(data); | |
379 | ||
380 | buf += '<tr><td class="field">' + key + '</td><td class="data">' + data + '</td></tr>'; | |
381 | } | |
c6511e85 LP |
382 | entry.innerHTML = '<tbody>' + buf + '</tbody>'; |
383 | } | |
384 | ||
385 | function onMessageClick(t) { | |
386 | var request = new XMLHttpRequest(); | |
387 | request.open("GET", "/entries?discrete"); | |
388 | request.onreadystatechange = onResultMessageClick; | |
389 | request.setRequestHeader("Accept", "application/json"); | |
390 | request.setRequestHeader("Range", "entries=" + t + ":0:1"); | |
391 | request.send(null); | |
bf5542f8 LP |
392 | } |
393 | ||
6d5f2f58 LP |
394 | function onKeyUp(event) { |
395 | switch (event.keyCode) { | |
396 | case 8: | |
397 | case 37: | |
398 | case 75: | |
399 | entriesLoadPrevious(); | |
400 | break; | |
401 | case 32: | |
402 | case 39: | |
403 | case 74: | |
404 | entriesLoadNext(); | |
405 | break; | |
406 | ||
407 | case 71: | |
408 | if (event.shiftKey) | |
409 | entriesLoadTail(); | |
410 | else | |
411 | entriesLoadHead(); | |
412 | break; | |
413 | case 171: | |
414 | entriesMore(); | |
415 | break; | |
416 | case 173: | |
417 | entriesLess(); | |
418 | break; | |
419 | } | |
420 | } | |
421 | ||
19f8efac KS |
422 | function onMouseWheel(event) { |
423 | if (event.detail < 0 || event.wheelDelta > 0) | |
424 | entriesLoadPrevious(); | |
425 | else | |
426 | entriesLoadNext(); | |
427 | } | |
428 | ||
bf5542f8 LP |
429 | machineLoad(); |
430 | entriesLoad(""); | |
431 | showNEntries(getNEntries()); | |
6d5f2f58 | 432 | document.onkeyup = onKeyUp; |
19f8efac KS |
433 | |
434 | logs = document.getElementById("tablelogs"); | |
435 | logs.addEventListener("mousewheel", onMouseWheel, false); | |
436 | logs.addEventListener("DOMMouseScroll", onMouseWheel, false); | |
bf5542f8 LP |
437 | </script> |
438 | </body> | |
439 | </html> |