]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/web/check_results.py
CheckResults corrupt/incompatible shares now return IServers
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / web / check_results.py
1
2 import time
3 import simplejson
4 from nevow import rend, inevow, tags as T
5 from twisted.web import http, html
6 from allmydata.web.common import getxmlfile, get_arg, get_root, WebError
7 from allmydata.web.operations import ReloadMixin
8 from allmydata.interfaces import ICheckAndRepairResults, ICheckResults
9 from allmydata.util import base32, dictutil
10
11 def json_check_counts(r):
12     d = {"count-shares-good": r.get_share_counter_good(),
13          "count-shares-needed": r.get_encoding_needed(),
14          "count-shares-expected": r.get_encoding_expected(),
15          "count-good-share-hosts": r.get_host_counter_good_shares(),
16          "count-corrupt-shares": len(r.get_corrupt_shares()),
17          "list-corrupt-shares": [ (s.get_longname(), base32.b2a(si), shnum)
18                                   for (s, si, shnum)
19                                   in r.get_corrupt_shares() ],
20          "servers-responding": [s.get_longname()
21                                 for s in r.get_servers_responding()],
22          "sharemap": dict([(shareid,
23                             sorted([s.get_longname() for s in servers]))
24                            for (shareid, servers)
25                            in r.get_sharemap().items()]),
26          "count-wrong-shares": r.get_share_counter_wrong(),
27          "count-recoverable-versions": r.get_version_counter_recoverable(),
28          "count-unrecoverable-versions": r.get_version_counter_unrecoverable(),
29          }
30     return d
31
32 def json_check_results(r):
33     if r is None:
34         # LIT file
35         data = {"storage-index": "",
36                 "results": {"healthy": True},
37                 }
38         return data
39     data = {}
40     data["storage-index"] = r.get_storage_index_string()
41     data["summary"] = r.get_summary()
42     data["results"] = json_check_counts(r)
43     data["results"]["needs-rebalancing"] = r.needs_rebalancing()
44     data["results"]["healthy"] = r.is_healthy()
45     data["results"]["recoverable"] = r.is_recoverable()
46     return data
47
48 def json_check_and_repair_results(r):
49     if r is None:
50         # LIT file
51         data = {"storage-index": "",
52                 "repair-attempted": False,
53                 }
54         return data
55     data = {}
56     data["storage-index"] = r.get_storage_index_string()
57     data["repair-attempted"] = r.get_repair_attempted()
58     data["repair-successful"] = r.get_repair_successful()
59     pre = r.get_pre_repair_results()
60     data["pre-repair-results"] = json_check_results(pre)
61     post = r.get_post_repair_results()
62     data["post-repair-results"] = json_check_results(post)
63     return data
64
65 class ResultsBase:
66     # self.client must point to the Client, so we can get nicknames and
67     # determine the permuted peer order
68
69     def _join_pathstring(self, path):
70         if path:
71             pathstring = "/".join(self._html(path))
72         else:
73             pathstring = "<root>"
74         return pathstring
75
76     def _render_results(self, ctx, cr):
77         assert ICheckResults(cr)
78         c = self.client
79         sb = c.get_storage_broker()
80         r = []
81         def add(name, value):
82             r.append(T.li[name + ": ", value])
83
84         add("Report", T.pre["\n".join(self._html(cr.get_report()))])
85         add("Share Counts",
86             "need %d-of-%d, have %d" % (cr.get_encoding_needed(),
87                                         cr.get_encoding_expected(),
88                                         cr.get_share_counter_good()))
89         add("Hosts with good shares", cr.get_host_counter_good_shares())
90
91         if cr.get_corrupt_shares():
92             badsharemap = []
93             for (s, si, shnum) in cr.get_corrupt_shares():
94                 d = T.tr[T.td["sh#%d" % shnum],
95                          T.td[T.div(class_="nickname")[s.get_nickname()],
96                               T.div(class_="nodeid")[T.tt[s.get_name()]]],
97                          ]
98                 badsharemap.append(d)
99             add("Corrupt shares", T.table()[
100                 T.tr[T.th["Share ID"],
101                      T.th(class_="nickname-and-peerid")[T.div["Nickname"], T.div(class_="nodeid")["Node ID"]]],
102                 badsharemap])
103         else:
104             add("Corrupt shares", "none")
105
106         add("Wrong Shares", cr.get_share_counter_wrong())
107
108         sharemap_data = []
109         shares_on_server = dictutil.DictOfSets()
110
111         # FIXME: The two tables below contain nickname-and-nodeid table column markup which is duplicated with each other, introducer.xhtml, and deep-check-results.xhtml. All of these (and any other presentations of nickname-and-nodeid) should be combined.
112
113         for shareid in sorted(cr.get_sharemap().keys()):
114             servers = sorted(cr.get_sharemap()[shareid],
115                              key=lambda s: s.get_longname())
116             for i,s in enumerate(servers):
117                 shares_on_server.add(s, shareid)
118                 shareid_s = ""
119                 if i == 0:
120                     shareid_s = shareid
121                 d = T.tr[T.td[shareid_s],
122                          T.td[T.div(class_="nickname")[s.get_nickname()],
123                               T.div(class_="nodeid")[T.tt[s.get_name()]]]
124                          ]
125                 sharemap_data.append(d)
126         add("Good Shares (sorted in share order)",
127             T.table()[T.tr[T.th["Share ID"], T.th(class_="nickname-and-peerid")[T.div["Nickname"], T.div(class_="nodeid")["Node ID"]]],
128                       sharemap_data])
129
130
131         add("Recoverable Versions", cr.get_version_counter_recoverable())
132         add("Unrecoverable Versions", cr.get_version_counter_unrecoverable())
133
134         # this table is sorted by permuted order
135         permuted_servers = [s
136                             for s
137                             in sb.get_servers_for_psi(cr.get_storage_index())]
138
139         num_shares_left = sum([len(shareids)
140                                for shareids in shares_on_server.values()])
141         servermap = []
142         for s in permuted_servers:
143             shareids = list(shares_on_server.get(s, []))
144             shareids.reverse()
145             shareids_s = [ T.tt[shareid, " "] for shareid in sorted(shareids) ]
146             d = T.tr[T.td[T.div(class_="nickname")[s.get_nickname()],
147                           T.div(class_="nodeid")[T.tt[s.get_name()]]],
148                      T.td[shareids_s],
149                      ]
150             servermap.append(d)
151             num_shares_left -= len(shareids)
152             if not num_shares_left:
153                 break
154         add("Share Balancing (servers in permuted order)",
155             T.table()[T.tr[T.th(class_="nickname-and-peerid")[T.div["Nickname"], T.div(class_="nodeid")["Node ID"]], T.th["Share IDs"]],
156                       servermap])
157
158         return T.ul[r]
159
160     def _html(self, s):
161         if isinstance(s, (str, unicode)):
162             return html.escape(s)
163         assert isinstance(s, (list, tuple))
164         return [html.escape(w) for w in s]
165
166     def want_json(self, ctx):
167         output = get_arg(inevow.IRequest(ctx), "output", "").lower()
168         if output.lower() == "json":
169             return True
170         return False
171
172     def _render_si_link(self, ctx, storage_index):
173         si_s = base32.b2a(storage_index)
174         req = inevow.IRequest(ctx)
175         ophandle = req.prepath[-1]
176         target = "%s/operations/%s/%s" % (get_root(ctx), ophandle, si_s)
177         output = get_arg(ctx, "output")
178         if output:
179             target = target + "?output=%s" % output
180         return T.a(href=target)[si_s]
181
182 class LiteralCheckResultsRenderer(rend.Page, ResultsBase):
183     docFactory = getxmlfile("literal-check-results.xhtml")
184
185     def __init__(self, client):
186         self.client = client
187         rend.Page.__init__(self, client)
188
189     def renderHTTP(self, ctx):
190         if self.want_json(ctx):
191             return self.json(ctx)
192         return rend.Page.renderHTTP(self, ctx)
193
194     def json(self, ctx):
195         inevow.IRequest(ctx).setHeader("content-type", "text/plain")
196         data = json_check_results(None)
197         return simplejson.dumps(data, indent=1) + "\n"
198
199     def render_return(self, ctx, data):
200         req = inevow.IRequest(ctx)
201         return_to = get_arg(req, "return_to", None)
202         if return_to:
203             return T.div[T.a(href=return_to)["Return to file."]]
204         return ""
205
206 class CheckerBase:
207
208     def renderHTTP(self, ctx):
209         if self.want_json(ctx):
210             return self.json(ctx)
211         return rend.Page.renderHTTP(self, ctx)
212
213     def render_storage_index(self, ctx, data):
214         return self.r.get_storage_index_string()
215
216     def render_return(self, ctx, data):
217         req = inevow.IRequest(ctx)
218         return_to = get_arg(req, "return_to", None)
219         if return_to:
220             return T.div[T.a(href=return_to)["Return to file/directory."]]
221         return ""
222
223 class CheckResultsRenderer(CheckerBase, rend.Page, ResultsBase):
224     docFactory = getxmlfile("check-results.xhtml")
225
226     def __init__(self, client, results):
227         self.client = client
228         self.r = ICheckResults(results)
229         rend.Page.__init__(self, results)
230
231     def json(self, ctx):
232         inevow.IRequest(ctx).setHeader("content-type", "text/plain")
233         data = json_check_results(self.r)
234         return simplejson.dumps(data, indent=1) + "\n"
235
236     def render_summary(self, ctx, data):
237         results = []
238         if data.is_healthy():
239             results.append("Healthy")
240         elif data.is_recoverable():
241             results.append("Not Healthy!")
242         else:
243             results.append("Not Recoverable!")
244         results.append(" : ")
245         results.append(self._html(data.get_summary()))
246         return ctx.tag[results]
247
248     def render_repair(self, ctx, data):
249         if data.is_healthy():
250             return ""
251         repair = T.form(action=".", method="post",
252                         enctype="multipart/form-data")[
253             T.fieldset[
254             T.input(type="hidden", name="t", value="check"),
255             T.input(type="hidden", name="repair", value="true"),
256             T.input(type="submit", value="Repair"),
257             ]]
258         return "" # repair button disabled until we make it work correctly,
259                   # see #622 for details
260         return ctx.tag[repair]
261
262     def render_results(self, ctx, data):
263         cr = self._render_results(ctx, data)
264         return ctx.tag[cr]
265
266 class CheckAndRepairResultsRenderer(CheckerBase, rend.Page, ResultsBase):
267     docFactory = getxmlfile("check-and-repair-results.xhtml")
268
269     def __init__(self, client, results):
270         self.client = client
271         self.r = None
272         if results:
273             self.r = ICheckAndRepairResults(results)
274         rend.Page.__init__(self, results)
275
276     def json(self, ctx):
277         inevow.IRequest(ctx).setHeader("content-type", "text/plain")
278         data = json_check_and_repair_results(self.r)
279         return simplejson.dumps(data, indent=1) + "\n"
280
281     def render_summary(self, ctx, data):
282         cr = data.get_post_repair_results()
283         results = []
284         if cr.is_healthy():
285             results.append("Healthy")
286         elif cr.is_recoverable():
287             results.append("Not Healthy!")
288         else:
289             results.append("Not Recoverable!")
290         results.append(" : ")
291         results.append(self._html(cr.get_summary()))
292         return ctx.tag[results]
293
294     def render_repair_results(self, ctx, data):
295         if data.get_repair_attempted():
296             if data.get_repair_successful():
297                 return ctx.tag["Repair successful"]
298             else:
299                 return ctx.tag["Repair unsuccessful"]
300         return ctx.tag["No repair necessary"]
301
302     def render_post_repair_results(self, ctx, data):
303         cr = self._render_results(ctx, data.get_post_repair_results())
304         return ctx.tag[T.div["Post-Repair Checker Results:"], cr]
305
306     def render_maybe_pre_repair_results(self, ctx, data):
307         if data.get_repair_attempted():
308             cr = self._render_results(ctx, data.get_pre_repair_results())
309             return ctx.tag[T.div["Pre-Repair Checker Results:"], cr]
310         return ""
311
312
313 class DeepCheckResultsRenderer(rend.Page, ResultsBase, ReloadMixin):
314     docFactory = getxmlfile("deep-check-results.xhtml")
315
316     def __init__(self, client, monitor):
317         self.client = client
318         self.monitor = monitor
319
320     def childFactory(self, ctx, name):
321         if not name:
322             return self
323         # /operation/$OPHANDLE/$STORAGEINDEX provides detailed information
324         # about a specific file or directory that was checked
325         si = base32.a2b(name)
326         r = self.monitor.get_status()
327         try:
328             return CheckResultsRenderer(self.client,
329                                         r.get_results_for_storage_index(si))
330         except KeyError:
331             raise WebError("No detailed results for SI %s" % html.escape(name),
332                            http.NOT_FOUND)
333
334     def renderHTTP(self, ctx):
335         if self.want_json(ctx):
336             return self.json(ctx)
337         return rend.Page.renderHTTP(self, ctx)
338
339     def json(self, ctx):
340         inevow.IRequest(ctx).setHeader("content-type", "text/plain")
341         data = {}
342         data["finished"] = self.monitor.is_finished()
343         res = self.monitor.get_status()
344         data["root-storage-index"] = res.get_root_storage_index_string()
345         c = res.get_counters()
346         data["count-objects-checked"] = c["count-objects-checked"]
347         data["count-objects-healthy"] = c["count-objects-healthy"]
348         data["count-objects-unhealthy"] = c["count-objects-unhealthy"]
349         data["count-corrupt-shares"] = c["count-corrupt-shares"]
350         data["list-corrupt-shares"] = [ (s.get_longname(),
351                                          base32.b2a(storage_index),
352                                          shnum)
353                                         for (s, storage_index, shnum)
354                                         in res.get_corrupt_shares() ]
355         data["list-unhealthy-files"] = [ (path_t, json_check_results(r))
356                                          for (path_t, r)
357                                          in res.get_all_results().items()
358                                          if not r.is_healthy() ]
359         data["stats"] = res.get_stats()
360         return simplejson.dumps(data, indent=1) + "\n"
361
362     def render_root_storage_index(self, ctx, data):
363         return self.monitor.get_status().get_root_storage_index_string()
364
365     def data_objects_checked(self, ctx, data):
366         return self.monitor.get_status().get_counters()["count-objects-checked"]
367     def data_objects_healthy(self, ctx, data):
368         return self.monitor.get_status().get_counters()["count-objects-healthy"]
369     def data_objects_unhealthy(self, ctx, data):
370         return self.monitor.get_status().get_counters()["count-objects-unhealthy"]
371     def data_objects_unrecoverable(self, ctx, data):
372         return self.monitor.get_status().get_counters()["count-objects-unrecoverable"]
373
374     def data_count_corrupt_shares(self, ctx, data):
375         return self.monitor.get_status().get_counters()["count-corrupt-shares"]
376
377     def render_problems_p(self, ctx, data):
378         c = self.monitor.get_status().get_counters()
379         if c["count-objects-unhealthy"]:
380             return ctx.tag
381         return ""
382
383     def data_problems(self, ctx, data):
384         all_objects = self.monitor.get_status().get_all_results()
385         for path in sorted(all_objects.keys()):
386             cr = all_objects[path]
387             assert ICheckResults.providedBy(cr)
388             if not cr.is_healthy():
389                 yield path, cr
390
391     def render_problem(self, ctx, data):
392         path, cr = data
393         summary_text = ""
394         summary = cr.get_summary()
395         if summary:
396             summary_text = ": " + summary
397         summary_text += " [SI: %s]" % cr.get_storage_index_string()
398         return ctx.tag[self._join_pathstring(path), self._html(summary_text)]
399
400
401     def render_servers_with_corrupt_shares_p(self, ctx, data):
402         if self.monitor.get_status().get_counters()["count-corrupt-shares"]:
403             return ctx.tag
404         return ""
405
406     def data_servers_with_corrupt_shares(self, ctx, data):
407         servers = [s
408                    for (s, storage_index, sharenum)
409                    in self.monitor.get_status().get_corrupt_shares()]
410         servers.sort(key=lambda s: s.get_longname())
411         return servers
412
413     def render_server_problem(self, ctx, server):
414         data = [server.get_name()]
415         nickname = server.get_nickname()
416         if nickname:
417             data.append(" (%s)" % self._html(nickname))
418         return ctx.tag[data]
419
420
421     def render_corrupt_shares_p(self, ctx, data):
422         if self.monitor.get_status().get_counters()["count-corrupt-shares"]:
423             return ctx.tag
424         return ""
425     def data_corrupt_shares(self, ctx, data):
426         return self.monitor.get_status().get_corrupt_shares()
427     def render_share_problem(self, ctx, data):
428         server, storage_index, sharenum = data
429         nickname = server.get_nickname()
430         ctx.fillSlots("serverid", server.get_name())
431         if nickname:
432             ctx.fillSlots("nickname", self._html(nickname))
433         ctx.fillSlots("si", self._render_si_link(ctx, storage_index))
434         ctx.fillSlots("shnum", str(sharenum))
435         return ctx.tag
436
437     def render_return(self, ctx, data):
438         req = inevow.IRequest(ctx)
439         return_to = get_arg(req, "return_to", None)
440         if return_to:
441             return T.div[T.a(href=return_to)["Return to file/directory."]]
442         return ""
443
444     def data_all_objects(self, ctx, data):
445         r = self.monitor.get_status().get_all_results()
446         for path in sorted(r.keys()):
447             yield (path, r[path])
448
449     def render_object(self, ctx, data):
450         path, r = data
451         ctx.fillSlots("path", self._join_pathstring(path))
452         ctx.fillSlots("healthy", str(r.is_healthy()))
453         ctx.fillSlots("recoverable", str(r.is_recoverable()))
454         storage_index = r.get_storage_index()
455         ctx.fillSlots("storage_index", self._render_si_link(ctx, storage_index))
456         ctx.fillSlots("summary", self._html(r.get_summary()))
457         return ctx.tag
458
459     def render_runtime(self, ctx, data):
460         req = inevow.IRequest(ctx)
461         runtime = time.time() - req.processing_started_timestamp
462         return ctx.tag["runtime: %s seconds" % runtime]
463
464 class DeepCheckAndRepairResultsRenderer(rend.Page, ResultsBase, ReloadMixin):
465     docFactory = getxmlfile("deep-check-and-repair-results.xhtml")
466
467     def __init__(self, client, monitor):
468         self.client = client
469         self.monitor = monitor
470
471     def childFactory(self, ctx, name):
472         if not name:
473             return self
474         # /operation/$OPHANDLE/$STORAGEINDEX provides detailed information
475         # about a specific file or directory that was checked
476         si = base32.a2b(name)
477         s = self.monitor.get_status()
478         try:
479             results = s.get_results_for_storage_index(si)
480             return CheckAndRepairResultsRenderer(self.client, results)
481         except KeyError:
482             raise WebError("No detailed results for SI %s" % html.escape(name),
483                            http.NOT_FOUND)
484
485     def renderHTTP(self, ctx):
486         if self.want_json(ctx):
487             return self.json(ctx)
488         return rend.Page.renderHTTP(self, ctx)
489
490     def json(self, ctx):
491         inevow.IRequest(ctx).setHeader("content-type", "text/plain")
492         res = self.monitor.get_status()
493         data = {}
494         data["finished"] = self.monitor.is_finished()
495         data["root-storage-index"] = res.get_root_storage_index_string()
496         c = res.get_counters()
497         data["count-objects-checked"] = c["count-objects-checked"]
498
499         data["count-objects-healthy-pre-repair"] = c["count-objects-healthy-pre-repair"]
500         data["count-objects-unhealthy-pre-repair"] = c["count-objects-unhealthy-pre-repair"]
501         data["count-objects-healthy-post-repair"] = c["count-objects-healthy-post-repair"]
502         data["count-objects-unhealthy-post-repair"] = c["count-objects-unhealthy-post-repair"]
503
504         data["count-repairs-attempted"] = c["count-repairs-attempted"]
505         data["count-repairs-successful"] = c["count-repairs-successful"]
506         data["count-repairs-unsuccessful"] = c["count-repairs-unsuccessful"]
507
508         data["count-corrupt-shares-pre-repair"] = c["count-corrupt-shares-pre-repair"]
509         data["count-corrupt-shares-post-repair"] = c["count-corrupt-shares-pre-repair"]
510
511         data["list-corrupt-shares"] = [ (s.get_longname(),
512                                          base32.b2a(storage_index),
513                                          shnum)
514                                         for (s, storage_index, shnum)
515                                         in res.get_corrupt_shares() ]
516
517         remaining_corrupt = [ (s.get_longname(), base32.b2a(storage_index),
518                                shnum)
519                               for (s, storage_index, shnum)
520                               in res.get_remaining_corrupt_shares() ]
521         data["list-remaining-corrupt-shares"] = remaining_corrupt
522
523         unhealthy = [ (path_t,
524                        json_check_results(crr.get_pre_repair_results()))
525                       for (path_t, crr)
526                       in res.get_all_results().items()
527                       if not crr.get_pre_repair_results().is_healthy() ]
528         data["list-unhealthy-files"] = unhealthy
529         data["stats"] = res.get_stats()
530         return simplejson.dumps(data, indent=1) + "\n"
531
532     def render_root_storage_index(self, ctx, data):
533         return self.monitor.get_status().get_root_storage_index_string()
534
535     def data_objects_checked(self, ctx, data):
536         return self.monitor.get_status().get_counters()["count-objects-checked"]
537
538     def data_objects_healthy(self, ctx, data):
539         return self.monitor.get_status().get_counters()["count-objects-healthy-pre-repair"]
540     def data_objects_unhealthy(self, ctx, data):
541         return self.monitor.get_status().get_counters()["count-objects-unhealthy-pre-repair"]
542     def data_corrupt_shares(self, ctx, data):
543         return self.monitor.get_status().get_counters()["count-corrupt-shares-pre-repair"]
544
545     def data_repairs_attempted(self, ctx, data):
546         return self.monitor.get_status().get_counters()["count-repairs-attempted"]
547     def data_repairs_successful(self, ctx, data):
548         return self.monitor.get_status().get_counters()["count-repairs-successful"]
549     def data_repairs_unsuccessful(self, ctx, data):
550         return self.monitor.get_status().get_counters()["count-repairs-unsuccessful"]
551
552     def data_objects_healthy_post(self, ctx, data):
553         return self.monitor.get_status().get_counters()["count-objects-healthy-post-repair"]
554     def data_objects_unhealthy_post(self, ctx, data):
555         return self.monitor.get_status().get_counters()["count-objects-unhealthy-post-repair"]
556     def data_corrupt_shares_post(self, ctx, data):
557         return self.monitor.get_status().get_counters()["count-corrupt-shares-post-repair"]
558
559     def render_pre_repair_problems_p(self, ctx, data):
560         c = self.monitor.get_status().get_counters()
561         if c["count-objects-unhealthy-pre-repair"]:
562             return ctx.tag
563         return ""
564
565     def data_pre_repair_problems(self, ctx, data):
566         all_objects = self.monitor.get_status().get_all_results()
567         for path in sorted(all_objects.keys()):
568             r = all_objects[path]
569             assert ICheckAndRepairResults.providedBy(r)
570             cr = r.get_pre_repair_results()
571             if not cr.is_healthy():
572                 yield path, cr
573
574     def render_problem(self, ctx, data):
575         path, cr = data
576         return ctx.tag[self._join_pathstring(path), ": ",
577                        self._html(cr.get_summary())]
578
579     def render_post_repair_problems_p(self, ctx, data):
580         c = self.monitor.get_status().get_counters()
581         if (c["count-objects-unhealthy-post-repair"]
582             or c["count-corrupt-shares-post-repair"]):
583             return ctx.tag
584         return ""
585
586     def data_post_repair_problems(self, ctx, data):
587         all_objects = self.monitor.get_status().get_all_results()
588         for path in sorted(all_objects.keys()):
589             r = all_objects[path]
590             assert ICheckAndRepairResults.providedBy(r)
591             cr = r.get_post_repair_results()
592             if not cr.is_healthy():
593                 yield path, cr
594
595     def render_servers_with_corrupt_shares_p(self, ctx, data):
596         if self.monitor.get_status().get_counters()["count-corrupt-shares-pre-repair"]:
597             return ctx.tag
598         return ""
599     def data_servers_with_corrupt_shares(self, ctx, data):
600         return [] # TODO
601     def render_server_problem(self, ctx, data):
602         pass
603
604
605     def render_remaining_corrupt_shares_p(self, ctx, data):
606         if self.monitor.get_status().get_counters()["count-corrupt-shares-post-repair"]:
607             return ctx.tag
608         return ""
609     def data_post_repair_corrupt_shares(self, ctx, data):
610         return [] # TODO
611
612     def render_share_problem(self, ctx, data):
613         pass
614
615
616     def render_return(self, ctx, data):
617         req = inevow.IRequest(ctx)
618         return_to = get_arg(req, "return_to", None)
619         if return_to:
620             return T.div[T.a(href=return_to)["Return to file/directory."]]
621         return ""
622
623     def data_all_objects(self, ctx, data):
624         r = self.monitor.get_status().get_all_results()
625         for path in sorted(r.keys()):
626             yield (path, r[path])
627
628     def render_object(self, ctx, data):
629         path, r = data
630         ctx.fillSlots("path", self._join_pathstring(path))
631         ctx.fillSlots("healthy_pre_repair",
632                       str(r.get_pre_repair_results().is_healthy()))
633         ctx.fillSlots("recoverable_pre_repair",
634                       str(r.get_pre_repair_results().is_recoverable()))
635         ctx.fillSlots("healthy_post_repair",
636                       str(r.get_post_repair_results().is_healthy()))
637         storage_index = r.get_storage_index()
638         ctx.fillSlots("storage_index",
639                       self._render_si_link(ctx, storage_index))
640         ctx.fillSlots("summary",
641                       self._html(r.get_pre_repair_results().get_summary()))
642         return ctx.tag
643
644     def render_runtime(self, ctx, data):
645         req = inevow.IRequest(ctx)
646         runtime = time.time() - req.processing_started_timestamp
647         return ctx.tag["runtime: %s seconds" % runtime]