check-bkp-status.in: JavaScript to expand/contract the log reports.
[rsync-backup] / check-bkp-status.in
1 #! @BASH@
2 ###
3 ### Check that dumps have been made as expected.
4 ###
5 ### (c) 2013 Mark Wooding
6 ###
7
8 ###----- Licensing notice ---------------------------------------------------
9 ###
10 ### This file is part of the `rsync-backup' program.
11 ###
12 ### rsync-backup is free software; you can redistribute it and/or modify
13 ### it under the terms of the GNU General Public License as published by
14 ### the Free Software Foundation; either version 2 of the License, or
15 ### (at your option) any later version.
16 ###
17 ### rsync-backup is distributed in the hope that it will be useful,
18 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ### GNU General Public License for more details.
21 ###
22 ### You should have received a copy of the GNU General Public License
23 ### along with rsync-backup; if not, write to the Free Software Foundation,
24 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 set -e
27
28 quis=${0##*/}
29 . @pkgdatadir@/lib.sh
30
31 ###--------------------------------------------------------------------------
32 ### Stubs for the configuration file.
33
34 defhook () { :; }
35 addhook () { :; }
36
37 retain () { :; }
38 snaptype () { :; }
39 like () { :; }
40 retry () { :; }
41 user () { :; }
42
43 ###--------------------------------------------------------------------------
44 ### Output format switch.
45
46 formats=:
47 defformat () { formats=$formats$1:; }
48 fmtstate=begin
49
50 fmt () {
51 state=$1; shift
52
53 while :; do
54 ostate=$fmtstate
55 case $fmtstate in $state) break ;; esac
56 case "$fmtstate,$state" in
57 begin,end)
58 fmtstate=end
59 ;;
60 begin,*)
61 ${fmt}_header "$@"
62 fmtstate=hosttbl_begin
63 ;;
64 hosttbl_begin,hosttbl_*)
65 ${fmt}_hosttbl_begin "$@"
66 fmtstate=hosttbl_host
67 ;;
68 hosttbl_host,hosttbl_fs)
69 fmtstate=hosttbl_fs
70 ;;
71 hosttbl_fs,hosttbl_host)
72 ${fmt}_hosttbl_sep "$@"
73 fmtstate=hosttbl_host
74 ;;
75 hosttbl_begin,* | hosttbl_end,*)
76 fmtstate=logdump_begin
77 ;;
78 hosttbl_*,*)
79 ${fmt}_hosttbl_end "$@"
80 fmtstate=hosttbl_end
81 ;;
82 logdump_begin,logdump_*)
83 fmtstate=logdump_out
84 ;;
85 logdump_out,logdump_info | logdump_out,logdump_file)
86 fmtstate=$state
87 ;;
88 logdump_*,logdump_begin)
89 ${fmt}_logdump_end "$@"
90 fmtstate=logdump_begin
91 ;;
92 logdump_end,*)
93 fmtstate=footer
94 ;;
95 logdump_*,*)
96 fmtstate=logdump_end
97 ;;
98 footer,end)
99 ${fmt}_footer "$@"
100 fmtstate=end
101 ;;
102 esac
103 case $fmtstate in
104 "$ostate")
105 echo >&2 "$quis: FATAL! NO PROGRESS IN FMT STATE MACHINE"
106 exit 9
107 ;;
108 esac
109 done
110
111 ${fmt}_$fmtstate "$@"
112 }
113
114 ###--------------------------------------------------------------------------
115 ### Plain text output.
116
117 defformat txt
118
119 txt_header () { :; }
120 txt_hosttbl_begin () { :; }
121 txt_hosttbl_host () { echo "HOST $1"; }
122 txt_hosttbl_fs () { printf " %-23s %s\n" "$2" "$4"; }
123 txt_hosttbl_sep () { echo; }
124 txt_hosttbl_end () { :; }
125
126 txt_logdump_begin () {
127 cat <<EOF
128
129 -----------------------------------------------------------------------------
130 Log tail for $1:$2
131
132 EOF
133 }
134
135 txt_logdump_info () { echo "!!! $3"; }
136 txt_logdump_file () { cat "$3"; }
137 txt_logdump_end () { :; }
138
139 txt_footer () { :; }
140 txt_end () { :; }
141
142 fmt=txt
143
144 ###--------------------------------------------------------------------------
145 ### HTML output.
146
147 defformat html
148
149 html_header () {
150 cat <<EOF
151 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
152 "http://www.w3c.org/TR/html4/strict.dtd">
153 <html>
154 <head>
155 <title>$now rsync-backup report</title>
156 <style type='text/css'><!--
157 body {
158 background: white;
159 color: black;
160 margin: 1ex 2em;
161 }
162
163 h1, h2 { font-family: sans-serif; font-weight: bold; }
164
165 h1 {
166 padding-bottom: 1ex;
167 border-bottom: solid medium black;
168 margin-bottom: 3ex;
169 font-size: 200%;
170 }
171
172 h2 {
173 border-top: solid thin black;
174 padding-top: 1ex;
175 margin-top: 3ex;
176 font-size: x-large;
177 }
178
179 h1 + h2 {
180 border-top: none;
181 padding-top: 0;
182 }
183
184 div.footer {
185 border-top: solid medium black;
186 margin-top: 3ex;
187 padding-top: 1ex;
188 font-size: small;
189 clear: both;
190 text-align: right;
191 font-style: italic;
192 }
193
194 table.hosttbl {
195 border-collapse: collapse;
196 display: inline-table;
197 margin: 1ex 1em;
198 }
199 table.hosttbl tr.fs td {
200 border: solid thin black;
201 }
202 table.hosttbl td { padding: 0.5ex 0.5em; }
203 table.hosttbl tr.host td {
204 padding-top: 2ex;
205 text-align: center;
206 font-weight: bold;
207 font-family: monospace;
208 }
209 table.hosttbl td.fs {
210 font-family: monospace;
211 }
212
213 table.hosttbl td.winning { background: #2f4; }
214 table.hosttbl td.failed { background: #f80; }
215 table.hosttbl td.broken { background: #f11; }
216 table.hosttbl td.never { background: #66f; }
217
218 pre {
219 clear: both;
220 margin: 3ex 2em;
221 padding: 1ex;
222 background: #eee;
223 border: solid thin black;
224 overflow-y: auto;
225 }
226
227 .hide { display: none; }
228 a.expand-button {
229 float: right;
230 font-size: medium;
231 font-weight: initial;
232 padding-top: 1ex;
233 }
234
235 div.logdump-info {
236 margin: 3ex 2em;
237 padding: 1ex;
238 background: #f11;
239 border: solid thin black;
240 }
241 --></style>
242 <script type='text/javascript'><!--
243 var LAST_EXPAND = null;
244 function elt(id) { return document.getElementById(id); }
245 function add_elt_class(elt, cls) {
246 if (!elt.className.match('\\\\b' + cls + '\\\\b'))
247 elt.className += ' ' + cls;
248 }
249 function rm_elt_class(elt, cls) {
250 elt.className = elt.className.replace(
251 new RegExp('\\\\s*\\\\b' + cls + '\\\\b\\\\s*'), ' ');
252 }
253 function toggle_expand(ev, tag) {
254 var d = elt('logdump-' + tag);
255 var b = elt('expand-' + tag);
256 if (d.className.match(/\bhide\b/)) do_show(d, b);
257 else do_hide(d, b);
258 ev.preventDefault();
259 }
260 function do_show(d, b) {
261 rm_elt_class(d, 'hide');
262 b.textContent = '[hide]';
263 }
264 function do_hide(d, b) {
265 add_elt_class(d, 'hide');
266 b.textContent = '[show]';
267 }
268 function expand_log(tag) {
269 if (LAST_EXPAND !== null)
270 do_hide(LAST_EXPAND[0], LAST_EXPAND[1]);
271 var d = elt('logdump-' + tag);
272 var b = elt('expand-' + tag);
273 LAST_EXPAND = [d, b];
274 do_show(d, b);
275 }
276 function make_toggle_button(tag) {
277 document.write(
278 "<a class=expand-button id='expand-" + tag + "' href='#' " +
279 "onclick='toggle_expand(event, \"" + tag + "\")'>" +
280 "[show]" +
281 "</a>");
282 }
283 function hide_logdump(tag) {
284 add_elt_class(elt('logdump-' + tag), 'hide');
285 }
286 --></script>
287 </head>
288 <body>
289
290 <h1><tt>rsync-backup</tt> report: $now_dow $now</tt></h1>
291 EOF
292 }
293
294 html_hosttbl_begin () {
295 cat <<EOF
296
297 <h2>Overview</h2>
298 EOF
299 }
300
301 html_hosttbl_host () {
302 cat <<EOF
303 <table class=hosttbl>
304 <tr class=host><td colspan=2>$1</tc>
305 EOF
306 }
307
308 html_hosttbl_fs () {
309 case $3 in
310 winning) link="" knil="" ;;
311 *)
312 link="<a href='#log-$host:$fs' onclick='expand_log(\"$host:$fs\")'>"
313 knil="</a>"
314 ;;
315 esac
316 cat <<EOF
317 <tr class=fs>
318 <td class=fs>$2</td>
319 <td class=$3>$link$4$knil</td>
320 EOF
321 }
322
323 html_hosttbl_sep () {
324 cat <<EOF
325 </table>
326 EOF
327 }
328
329 html_hosttbl_end () {
330 cat <<EOF
331 </table>
332 EOF
333 }
334
335 html_logdump_begin () {
336 cat <<EOF
337
338 <h2 id='log-$1:$2'><a name='log-$1:$2'>Log tail for <tt>$1:$2</tt></a>
339 <script type='text/javascript'><!--
340 make_toggle_button('$1:$2');
341 --></script></h2>
342 EOF
343 }
344
345 html_logdump_info () {
346 cat <<EOF
347 <div id='logdump-$1:$2' class=logdump-info><script type='text/javascript'><!--
348 hide_logdump('$1:$2');
349 --></script>$3</div>
350 EOF
351 }
352
353 html_logdump_file () {
354 cat <<EOF
355 <pre id='logdump-$1:$2' class=logdump>
356 <script type='text/javascript'><!--
357 hide_logdump('$1:$2');
358 --></script>$3</div>
359 EOF
360 cat "$3"
361 cat <<EOF
362 </pre>
363 EOF
364 }
365
366 html_logdump_end () { :; }
367
368 html_footer () {
369 cat <<EOF
370
371 <div class=footer>
372 Checked at $now $now_time.
373 <br><tt>rsync-backup</tt> $VERSION; &copy; 2014 Mark Wooding
374 </div>
375
376 </body>
377 </html>
378 EOF
379 }
380
381 html_end () { :; }
382
383 ###--------------------------------------------------------------------------
384 ### Main checking.
385
386 INDEXDB=@pkglocalstatedir@/index.db
387
388 host () { host=$1; }
389
390 patterns=
391 showhost=
392 failures=
393
394 backup () {
395 for fs in "$@"; do
396 case $fs in *:*) fs=${fs%%:*} ;; esac
397 matchp=nil
398 for p in "${patterns[@]}"; do
399 case $host:$fs in $p) matchp=t; break ;; esac
400 done
401 case $matchp in nil) return ;; esac
402 when=$(sqlite3 $INDEXDB \
403 "SELECT MAX(date) FROM idx WHERE host = '$host' AND fs = '$fs';")
404 case $when in
405 "")
406 class=never
407 info="NEVER"
408 show=t
409 win=nil
410 ;;
411 "$now")
412 class=winning
413 info="today"
414 show=$verbose
415 win=t
416 ;;
417 *)
418 jdn=$(julian "$when")
419 ago=$(( $now_jdn - $jdn ))
420 case $ago in 1 | -1) days=day ;; *) days=days ;; esac
421 case $ago in
422 -*) class=future; info="${ago#-} $days in the FUTURE" ;;
423 1 | 2) class=failed; info="$ago $days ago" ;;
424 *) class=broken; info="$ago $days ago" ;;
425 esac
426 show=t
427 win=nil
428 ;;
429 esac
430 case $show in
431 t)
432 case $showhost in
433 "$host") ;;
434 *)
435 fmt hosttbl_host $host
436 showhost=$host
437 ;;
438 esac
439 fmt hosttbl_fs $host $fs $class "$info"
440 case $win in
441 nil) failures="$failures $host:$fs" ;;
442 esac
443 ;;
444 esac
445 done
446 }
447
448 failure_logs () {
449 for fail in $failures; do
450 host=${fail%:*} fs=${fail##*:}
451 any=nil
452 for i in "$logdir/$host/$fs.$now#"*; do
453 if [ -f "$i" ]; then log=$i; any=t; fi
454 done
455 fmt logdump_begin $host $fs
456 case $any in
457 t) fmt logdump_file $host $fs "$log" ;;
458 nil) fmt logdump_info $host $fs "No log! No backup attempted." ;;
459 esac
460 done
461 }
462
463 ###--------------------------------------------------------------------------
464 ### Read the configuration and we're done.
465
466 usage () {
467 echo "usage: $quis [-v] [-c CONF] [-f FORMAT] [PATTERNS...]"
468 }
469
470 version () {
471 echo "$quis, rsync-backup version $VERSION"
472 }
473
474 verbose=nil
475
476 while getopts "hVc:f:v" opt; do
477 case "$opt" in
478 h) usage; exit 0 ;;
479 V) version; config; exit 0 ;;
480 c) conf=$OPTARG ;;
481 f)
482 case $formats in
483 *":$OPTARG:"*) ;;
484 *) echo >&2 "$0: unknown format \`$OPTARG'"; exit 1 ;;
485 esac
486 fmt=$OPTARG
487 ;;
488 v) verbose=t ;;
489 *) exit 1 ;;
490 esac
491 done
492 shift $((OPTIND - 1))
493 case $# in
494 0) declare -a patterns=("*") ;;
495 *) declare -a patterns=("$@") ;;
496 esac
497
498 now=$(date +"%Y-%m-%d")
499 now_time=$(date +"%H:%M:%S %z")
500 now_dow=$(date +"%A")
501 now_jdn=$(julian $now)
502 . "$conf"
503 failure_logs
504 fmt end
505
506 ###----- That's all, folks --------------------------------------------------