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
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, storage
18 from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
19 get_arg, RenderMixin, boolean_of_arg
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.
26 def __init__(self, client):
27 rend.Page.__init__(self, client)
30 def render_GET(self, ctx):
32 uri = get_arg(req, "uri", None)
34 raise WebError("GET /uri requires uri=")
35 there = url.URL.fromContext(ctx)
36 there = there.clear("uri")
37 # I thought about escaping the childcap that we attach to the URL
38 # here, but it seems that nevow does that for us.
39 there = there.child(uri)
42 def render_PUT(self, ctx):
44 # either "PUT /uri" to create an unlinked file, or
45 # "PUT /uri?t=mkdir" to create an unlinked directory
46 t = get_arg(req, "t", "").strip()
48 mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip())
50 return unlinked.PUTUnlinkedSSK(req, self.client)
52 return unlinked.PUTUnlinkedCHK(req, self.client)
54 return unlinked.PUTUnlinkedCreateDirectory(req, self.client)
55 errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
57 raise WebError(errmsg, http.BAD_REQUEST)
59 def render_POST(self, ctx):
60 # "POST /uri?t=upload&file=newfile" to upload an
61 # unlinked file or "POST /uri?t=mkdir" to create a
64 t = get_arg(req, "t", "").strip()
65 if t in ("", "upload"):
66 mutable = bool(get_arg(req, "mutable", "").strip())
68 return unlinked.POSTUnlinkedSSK(req, self.client)
70 return unlinked.POSTUnlinkedCHK(req, self.client)
72 return unlinked.POSTUnlinkedCreateDirectory(req, self.client)
73 errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
75 raise WebError(errmsg, http.BAD_REQUEST)
77 def childFactory(self, ctx, name):
78 # 'name' is expected to be a URI
80 node = self.client.create_node_from_uri(name)
81 return directory.make_handler_for(node, self.client)
82 except (TypeError, AssertionError):
83 raise WebError("'%s' is not a valid file- or directory- cap"
86 class FileHandler(rend.Page):
87 # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
88 # file can be downloaded correctly by tools like "wget".
90 def __init__(self, client):
91 rend.Page.__init__(self, client)
94 def childFactory(self, ctx, name):
96 if req.method not in ("GET", "HEAD"):
97 raise WebError("/file can only be used with GET or HEAD")
98 # 'name' must be a file URI
100 node = self.client.create_node_from_uri(name)
101 except (TypeError, AssertionError):
102 raise WebError("'%s' is not a valid file- or directory- cap"
104 if not IFileNode.providedBy(node):
105 raise WebError("'%s' is not a file-cap" % name)
106 return filenode.FileNodeDownloadHandler(self.client, node)
108 def renderHTTP(self, ctx):
109 raise WebError("/file must be followed by a file-cap and a name",
112 class IncidentReporter(RenderMixin, rend.Page):
113 def render_POST(self, ctx):
115 log.msg(format="User reports incident through web page: %(details)s",
116 details=get_arg(req, "details", ""),
117 level=log.WEIRD, umid="LkD9Pw")
118 req.setHeader("content-type", "text/plain")
119 return "Thank you for your report!"
121 class NoReliability(rend.Page):
122 docFactory = loaders.xmlstr('''\
123 <html xmlns:n="http://nevow.com/ns/nevow/0.1">
125 <title>AllMyData - Tahoe</title>
126 <link href="/webform_css" rel="stylesheet" type="text/css"/>
127 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
130 <h2>"Reliability" page not available</h2>
131 <p>Please install the python "NumPy" module to enable this page.</p>
136 class Root(rend.Page):
139 docFactory = getxmlfile("welcome.xhtml")
141 def __init__(self, client):
142 rend.Page.__init__(self, client)
144 self.child_operations = operations.OphandleTable()
146 s = client.getServiceNamed("storage")
149 self.child_storage = storage.StorageStatus(s)
151 self.child_uri = URIHandler(client)
152 self.child_cap = URIHandler(client)
154 self.child_file = FileHandler(client)
155 self.child_named = FileHandler(client)
156 self.child_status = status.Status(client) # TODO: use client.history
157 self.child_statistics = status.Statistics(client.stats_provider)
159 def child_helper_status(self, ctx):
160 # the Helper isn't attached until after the Tub starts, so this child
161 # needs to created on each request
163 helper = self.client.getServiceNamed("helper")
166 return status.HelperStatus(helper)
168 child_webform_css = webform.defaultCSS
169 child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
171 child_provisioning = provisioning.ProvisioningTool()
172 if reliability.is_available():
173 child_reliability = reliability.ReliabilityTool()
175 child_reliability = NoReliability()
177 child_report_incident = IncidentReporter()
178 #child_server # let's reserve this for storage-server-over-HTTP
180 # FIXME: This code is duplicated in root.py and introweb.py.
181 def data_version(self, ctx, data):
182 return get_package_versions_string()
183 def data_import_path(self, ctx, data):
184 return str(allmydata)
185 def data_my_nodeid(self, ctx, data):
186 return idlib.nodeid_b2a(self.client.nodeid)
187 def data_my_nickname(self, ctx, data):
188 return self.client.nickname
190 def render_services(self, ctx, data):
193 ss = self.client.getServiceNamed("storage")
194 stats = ss.get_stats()
195 if stats["storage_server.accepting_immutable_shares"]:
196 msg = "accepting new shares"
198 msg = "not accepting new shares (read-only)"
199 available = stats.get("storage_server.disk_avail")
200 if available is not None:
201 msg += ", %s available" % abbreviate_size(available)
202 ul[T.li[T.a(href="storage")["Storage Server"], ": ", msg]]
204 ul[T.li["Not running storage server"]]
207 h = self.client.getServiceNamed("helper")
208 stats = h.get_stats()
209 active_uploads = stats["chk_upload_helper.active_uploads"]
210 ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
212 ul[T.li["Not running helper"]]
216 def data_introducer_furl(self, ctx, data):
217 return self.client.introducer_furl
218 def data_connected_to_introducer(self, ctx, data):
219 if self.client.connected_to_introducer():
223 def data_helper_furl(self, ctx, data):
225 uploader = self.client.getServiceNamed("uploader")
228 furl, connected = uploader.get_helper_info()
230 def data_connected_to_helper(self, ctx, data):
232 uploader = self.client.getServiceNamed("uploader")
234 return "no" # we don't even have an Uploader
235 furl, connected = uploader.get_helper_info()
240 def data_known_storage_servers(self, ctx, data):
241 ic = self.client.introducer_client
243 for c in ic.get_all_connectors().values()
244 if c.service_name == "storage"]
247 def data_connected_storage_servers(self, ctx, data):
248 ic = self.client.introducer_client
249 return len(ic.get_all_connections_for("storage"))
251 def data_services(self, ctx, data):
252 ic = self.client.introducer_client
253 c = [ (service_name, nodeid, rsc)
254 for (nodeid, service_name), rsc
255 in ic.get_all_connectors().items() ]
259 def render_service_row(self, ctx, data):
260 (service_name, nodeid, rsc) = data
261 ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
262 ctx.fillSlots("nickname", rsc.nickname)
264 rhost = rsc.remote_host
265 if nodeid == self.client.nodeid:
266 rhost_s = "(loopback)"
267 elif isinstance(rhost, address.IPv4Address):
268 rhost_s = "%s:%d" % (rhost.host, rhost.port)
271 connected = "Yes: to " + rhost_s
272 since = rsc.last_connect_time
275 since = rsc.last_loss_time
277 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
278 ctx.fillSlots("connected", connected)
279 ctx.fillSlots("connected-bool", not not rsc.rref)
280 ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
281 ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
282 time.localtime(rsc.announcement_time)))
283 ctx.fillSlots("version", rsc.version)
284 ctx.fillSlots("service_name", rsc.service_name)
288 def render_download_form(self, ctx, data):
289 # this is a form where users can download files by URI
290 form = T.form(action="uri", method="get",
291 enctype="multipart/form-data")[
293 T.legend(class_="freeform-form-label")["Download a file"],
294 T.div["Tahoe-URI to download: ",
295 T.input(type="text", name="uri")],
296 T.div["Filename to download as: ",
297 T.input(type="text", name="filename")],
298 T.input(type="submit", value="Download!"),
302 def render_view_form(self, ctx, data):
303 # this is a form where users can download files by URI, or jump to a
305 form = T.form(action="uri", method="get",
306 enctype="multipart/form-data")[
308 T.legend(class_="freeform-form-label")["View a file or directory"],
309 "Tahoe-URI to view: ",
310 T.input(type="text", name="uri"), " ",
311 T.input(type="submit", value="View!"),
315 def render_upload_form(self, ctx, data):
316 # this is a form where users can upload unlinked files
317 form = T.form(action="uri", method="post",
318 enctype="multipart/form-data")[
320 T.legend(class_="freeform-form-label")["Upload a file"],
321 T.div["Choose a file: ",
322 T.input(type="file", name="file", class_="freeform-input-file")],
323 T.input(type="hidden", name="t", value="upload"),
324 T.div[T.input(type="checkbox", name="mutable"), T.label(for_="mutable")["Create mutable file"],
325 " ", T.input(type="submit", value="Upload!")],
329 def render_mkdir_form(self, ctx, data):
330 # this is a form where users can create new directories
331 form = T.form(action="uri", method="post",
332 enctype="multipart/form-data")[
334 T.legend(class_="freeform-form-label")["Create a directory"],
335 T.input(type="hidden", name="t", value="mkdir"),
336 T.input(type="hidden", name="redirect_to_result", value="true"),
337 T.input(type="submit", value="Create a directory"),
341 def render_incident_button(self, ctx, data):
342 # this button triggers a foolscap-logging "incident"
343 form = T.form(action="report_incident", method="post",
344 enctype="multipart/form-data")[
346 T.legend(class_="freeform-form-label")["Report an Incident"],
347 T.input(type="hidden", name="t", value="report-incident"),
348 "What went wrong?: ",
349 T.input(type="text", name="details"), " ",
350 T.input(type="submit", value="Report!"),