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