]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/web/root.py
web: fix handling of reliability page when Numeric is not available
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / web / root.py
1 import time
2
3 from twisted.internet import address
4 from twisted.web import http
5 from nevow import rend, url, loaders, tags as T
6 from nevow.inevow import IRequest
7 from nevow.static import File as nevow_File # TODO: merge with static.File?
8 from nevow.util import resource_filename
9 from formless import webform
10
11 import allmydata # to display import path
12 from allmydata import get_package_versions_string
13 from allmydata import provisioning
14 from allmydata.util import idlib, log
15 from allmydata.interfaces import IFileNode
16 from allmydata.web import filenode, directory, unlinked, status, operations
17 from allmydata.web import reliability
18 from allmydata.web.common import abbreviate_size, IClient, \
19      getxmlfile, WebError, get_arg, RenderMixin
20
21
22 class URIHandler(RenderMixin, rend.Page):
23     # I live at /uri . There are several operations defined on /uri itself,
24     # mostly involved with creation of unlinked files and directories.
25
26     def render_GET(self, ctx):
27         req = IRequest(ctx)
28         uri = get_arg(req, "uri", None)
29         if uri is None:
30             raise WebError("GET /uri requires uri=")
31         there = url.URL.fromContext(ctx)
32         there = there.clear("uri")
33         # I thought about escaping the childcap that we attach to the URL
34         # here, but it seems that nevow does that for us.
35         there = there.child(uri)
36         return there
37
38     def render_PUT(self, ctx):
39         req = IRequest(ctx)
40         # either "PUT /uri" to create an unlinked file, or
41         # "PUT /uri?t=mkdir" to create an unlinked directory
42         t = get_arg(req, "t", "").strip()
43         if t == "":
44             mutable = bool(get_arg(req, "mutable", "").strip())
45             if mutable:
46                 return unlinked.PUTUnlinkedSSK(ctx)
47             else:
48                 return unlinked.PUTUnlinkedCHK(ctx)
49         if t == "mkdir":
50             return unlinked.PUTUnlinkedCreateDirectory(ctx)
51         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
52                   "and POST?t=mkdir")
53         raise WebError(errmsg, http.BAD_REQUEST)
54
55     def render_POST(self, ctx):
56         # "POST /uri?t=upload&file=newfile" to upload an
57         # unlinked file or "POST /uri?t=mkdir" to create a
58         # new directory
59         req = IRequest(ctx)
60         t = get_arg(req, "t", "").strip()
61         if t in ("", "upload"):
62             mutable = bool(get_arg(req, "mutable", "").strip())
63             if mutable:
64                 return unlinked.POSTUnlinkedSSK(ctx)
65             else:
66                 return unlinked.POSTUnlinkedCHK(ctx)
67         if t == "mkdir":
68             return unlinked.POSTUnlinkedCreateDirectory(ctx)
69         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
70                   "and POST?t=mkdir")
71         raise WebError(errmsg, http.BAD_REQUEST)
72
73     def childFactory(self, ctx, name):
74         # 'name' is expected to be a URI
75         client = IClient(ctx)
76         try:
77             node = client.create_node_from_uri(name)
78             return directory.make_handler_for(node)
79         except (TypeError, AssertionError):
80             raise WebError("'%s' is not a valid file- or directory- cap"
81                            % name)
82
83 class FileHandler(rend.Page):
84     # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
85     # file can be downloaded correctly by tools like "wget".
86
87     def childFactory(self, ctx, name):
88         req = IRequest(ctx)
89         if req.method not in ("GET", "HEAD"):
90             raise WebError("/file can only be used with GET or HEAD")
91         # 'name' must be a file URI
92         client = IClient(ctx)
93         try:
94             node = client.create_node_from_uri(name)
95         except (TypeError, AssertionError):
96             raise WebError("'%s' is not a valid file- or directory- cap"
97                            % name)
98         if not IFileNode.providedBy(node):
99             raise WebError("'%s' is not a file-cap" % name)
100         return filenode.FileNodeDownloadHandler(node)
101
102     def renderHTTP(self, ctx):
103         raise WebError("/file must be followed by a file-cap and a name",
104                        http.NOT_FOUND)
105
106 class IncidentReporter(RenderMixin, rend.Page):
107     def render_POST(self, ctx):
108         req = IRequest(ctx)
109         log.msg(format="User reports incident through web page: %(details)s",
110                 details=get_arg(req, "details", ""),
111                 level=log.WEIRD, umid="LkD9Pw")
112         req.setHeader("content-type", "text/plain")
113         return "Thank you for your report!"
114
115 class NoReliability(rend.Page):
116     docFactory = loaders.xmlstr('''\
117 <html xmlns:n="http://nevow.com/ns/nevow/0.1">
118   <head>
119     <title>AllMyData - Tahoe</title>
120     <link href="/webform_css" rel="stylesheet" type="text/css"/>
121     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
122   </head>
123   <body>
124   <h2>"Reliability" page not available</h2>
125   <p>Please install the python "Numeric" module to enable this page.</p>
126   </body>
127 </html>
128 ''')
129
130 class Root(rend.Page):
131
132     addSlash = True
133     docFactory = getxmlfile("welcome.xhtml")
134
135     def __init__(self, original=None):
136         rend.Page.__init__(self, original)
137         self.child_operations = operations.OphandleTable()
138
139     child_uri = URIHandler()
140     child_cap = URIHandler()
141     child_file = FileHandler()
142     child_named = FileHandler()
143
144     child_webform_css = webform.defaultCSS
145     child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
146
147     child_provisioning = provisioning.ProvisioningTool()
148     if reliability.is_available():
149         child_reliability = reliability.ReliabilityTool()
150     else:
151         child_reliability = NoReliability()
152     child_status = status.Status()
153     child_helper_status = status.HelperStatus()
154     child_statistics = status.Statistics()
155
156     child_report_incident = IncidentReporter()
157
158     def data_version(self, ctx, data):
159         return get_package_versions_string()
160     def data_import_path(self, ctx, data):
161         return str(allmydata)
162     def data_my_nodeid(self, ctx, data):
163         return idlib.nodeid_b2a(IClient(ctx).nodeid)
164     def data_my_nickname(self, ctx, data):
165         return IClient(ctx).nickname
166
167     def render_services(self, ctx, data):
168         ul = T.ul()
169         client = IClient(ctx)
170         try:
171             ss = client.getServiceNamed("storage")
172             allocated_s = abbreviate_size(ss.allocated_size())
173             allocated = "about %s allocated" % allocated_s
174             reserved = "%s reserved" % abbreviate_size(ss.reserved_space)
175             ul[T.li["Storage Server: %s, %s" % (allocated, reserved)]]
176         except KeyError:
177             ul[T.li["Not running storage server"]]
178
179         try:
180             h = client.getServiceNamed("helper")
181             stats = h.get_stats()
182             active_uploads = stats["chk_upload_helper.active_uploads"]
183             ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
184         except KeyError:
185             ul[T.li["Not running helper"]]
186
187         return ctx.tag[ul]
188
189     def data_introducer_furl(self, ctx, data):
190         return IClient(ctx).introducer_furl
191     def data_connected_to_introducer(self, ctx, data):
192         if IClient(ctx).connected_to_introducer():
193             return "yes"
194         return "no"
195
196     def data_helper_furl(self, ctx, data):
197         try:
198             uploader = IClient(ctx).getServiceNamed("uploader")
199         except KeyError:
200             return None
201         furl, connected = uploader.get_helper_info()
202         return furl
203     def data_connected_to_helper(self, ctx, data):
204         try:
205             uploader = IClient(ctx).getServiceNamed("uploader")
206         except KeyError:
207             return "no" # we don't even have an Uploader
208         furl, connected = uploader.get_helper_info()
209         if connected:
210             return "yes"
211         return "no"
212
213     def data_known_storage_servers(self, ctx, data):
214         ic = IClient(ctx).introducer_client
215         servers = [c
216                    for c in ic.get_all_connectors().values()
217                    if c.service_name == "storage"]
218         return len(servers)
219
220     def data_connected_storage_servers(self, ctx, data):
221         ic = IClient(ctx).introducer_client
222         return len(ic.get_all_connections_for("storage"))
223
224     def data_services(self, ctx, data):
225         ic = IClient(ctx).introducer_client
226         c = [ (service_name, nodeid, rsc)
227               for (nodeid, service_name), rsc
228               in ic.get_all_connectors().items() ]
229         c.sort()
230         return c
231
232     def render_service_row(self, ctx, data):
233         (service_name, nodeid, rsc) = data
234         ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
235         ctx.fillSlots("nickname", rsc.nickname)
236         if rsc.rref:
237             rhost = rsc.remote_host
238             if nodeid == IClient(ctx).nodeid:
239                 rhost_s = "(loopback)"
240             elif isinstance(rhost, address.IPv4Address):
241                 rhost_s = "%s:%d" % (rhost.host, rhost.port)
242             else:
243                 rhost_s = str(rhost)
244             connected = "Yes: to " + rhost_s
245             since = rsc.last_connect_time
246         else:
247             connected = "No"
248             since = rsc.last_loss_time
249
250         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
251         ctx.fillSlots("connected", connected)
252         ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
253         ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
254                                                  time.localtime(rsc.announcement_time)))
255         ctx.fillSlots("version", rsc.version)
256         ctx.fillSlots("service_name", rsc.service_name)
257
258         return ctx.tag
259
260     def render_download_form(self, ctx, data):
261         # this is a form where users can download files by URI
262         form = T.form(action="uri", method="get",
263                       enctype="multipart/form-data")[
264             T.fieldset[
265             T.legend(class_="freeform-form-label")["Download a file"],
266             "URI to download: ",
267             T.input(type="text", name="uri"), " ",
268             "Filename to download as: ",
269             T.input(type="text", name="filename"), " ",
270             T.input(type="submit", value="Download!"),
271             ]]
272         return T.div[form]
273
274     def render_view_form(self, ctx, data):
275         # this is a form where users can download files by URI, or jump to a
276         # named directory
277         form = T.form(action="uri", method="get",
278                       enctype="multipart/form-data")[
279             T.fieldset[
280             T.legend(class_="freeform-form-label")["View a file or directory"],
281             "URI to view: ",
282             T.input(type="text", name="uri"), " ",
283             T.input(type="submit", value="View!"),
284             ]]
285         return T.div[form]
286
287     def render_upload_form(self, ctx, data):
288         # this is a form where users can upload unlinked files
289         form = T.form(action="uri", method="post",
290                       enctype="multipart/form-data")[
291             T.fieldset[
292             T.legend(class_="freeform-form-label")["Upload a file"],
293             "Choose a file: ",
294             T.input(type="file", name="file", class_="freeform-input-file"),
295             T.input(type="hidden", name="t", value="upload"),
296             " Mutable?:", T.input(type="checkbox", name="mutable"),
297             T.input(type="submit", value="Upload!"),
298             ]]
299         return T.div[form]
300
301     def render_mkdir_form(self, ctx, data):
302         # this is a form where users can create new directories
303         form = T.form(action="uri", method="post",
304                       enctype="multipart/form-data")[
305             T.fieldset[
306             T.legend(class_="freeform-form-label")["Create a directory"],
307             T.input(type="hidden", name="t", value="mkdir"),
308             T.input(type="hidden", name="redirect_to_result", value="true"),
309             T.input(type="submit", value="Create Directory!"),
310             ]]
311         return T.div[form]
312
313     def render_incident_button(self, ctx, data):
314         # this button triggers a foolscap-logging "incident"
315         form = T.form(action="report_incident", method="post",
316                       enctype="multipart/form-data")[
317             T.fieldset[
318             T.legend(class_="freeform-form-label")["Report an Incident"],
319             T.input(type="hidden", name="t", value="report-incident"),
320             "What went wrong?: ",
321             T.input(type="text", name="details"), " ",
322             T.input(type="submit", value="Report!"),
323             ]]
324         return T.div[form]