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