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, \
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 = bool(get_arg(req, "mutable", "").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 self.child_uri = URIHandler(client)
147 self.child_cap = URIHandler(client)
149 self.child_file = FileHandler(client)
150 self.child_named = FileHandler(client)
151 self.child_status = status.Status(client) # TODO: use client.history
152 self.child_statistics = status.Statistics(client.stats_provider)
154 def child_helper_status(self, ctx):
155 # the Helper isn't attached until after the Tub starts, so this child
156 # needs to created on each request
158 helper = self.client.getServiceNamed("helper")
161 return status.HelperStatus(helper)
163 child_webform_css = webform.defaultCSS
164 child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
166 child_provisioning = provisioning.ProvisioningTool()
167 if reliability.is_available():
168 child_reliability = reliability.ReliabilityTool()
170 child_reliability = NoReliability()
172 child_report_incident = IncidentReporter()
174 def data_version(self, ctx, data):
175 return get_package_versions_string()
176 def data_import_path(self, ctx, data):
177 return str(allmydata)
178 def data_my_nodeid(self, ctx, data):
179 return idlib.nodeid_b2a(self.client.nodeid)
180 def data_my_nickname(self, ctx, data):
181 return self.client.nickname
183 def render_services(self, ctx, data):
186 ss = self.client.getServiceNamed("storage")
187 allocated_s = abbreviate_size(ss.allocated_size())
188 allocated = "about %s allocated" % allocated_s
189 reserved = "%s reserved" % abbreviate_size(ss.reserved_space)
190 ul[T.li["Storage Server: %s, %s" % (allocated, reserved)]]
192 ul[T.li["Not running storage server"]]
195 h = self.client.getServiceNamed("helper")
196 stats = h.get_stats()
197 active_uploads = stats["chk_upload_helper.active_uploads"]
198 ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
200 ul[T.li["Not running helper"]]
204 def data_introducer_furl(self, ctx, data):
205 return self.client.introducer_furl
206 def data_connected_to_introducer(self, ctx, data):
207 if self.client.connected_to_introducer():
211 def data_helper_furl(self, ctx, data):
213 uploader = self.client.getServiceNamed("uploader")
216 furl, connected = uploader.get_helper_info()
218 def data_connected_to_helper(self, ctx, data):
220 uploader = self.client.getServiceNamed("uploader")
222 return "no" # we don't even have an Uploader
223 furl, connected = uploader.get_helper_info()
228 def data_known_storage_servers(self, ctx, data):
229 ic = self.client.introducer_client
231 for c in ic.get_all_connectors().values()
232 if c.service_name == "storage"]
235 def data_connected_storage_servers(self, ctx, data):
236 ic = self.client.introducer_client
237 return len(ic.get_all_connections_for("storage"))
239 def data_services(self, ctx, data):
240 ic = self.client.introducer_client
241 c = [ (service_name, nodeid, rsc)
242 for (nodeid, service_name), rsc
243 in ic.get_all_connectors().items() ]
247 def render_service_row(self, ctx, data):
248 (service_name, nodeid, rsc) = data
249 ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
250 ctx.fillSlots("nickname", rsc.nickname)
252 rhost = rsc.remote_host
253 if nodeid == self.client.nodeid:
254 rhost_s = "(loopback)"
255 elif isinstance(rhost, address.IPv4Address):
256 rhost_s = "%s:%d" % (rhost.host, rhost.port)
259 connected = "Yes: to " + rhost_s
260 since = rsc.last_connect_time
263 since = rsc.last_loss_time
265 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
266 ctx.fillSlots("connected", connected)
267 ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
268 ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
269 time.localtime(rsc.announcement_time)))
270 ctx.fillSlots("version", rsc.version)
271 ctx.fillSlots("service_name", rsc.service_name)
275 def render_download_form(self, ctx, data):
276 # this is a form where users can download files by URI
277 form = T.form(action="uri", method="get",
278 enctype="multipart/form-data")[
280 T.legend(class_="freeform-form-label")["Download a file"],
282 T.input(type="text", name="uri"), " ",
283 "Filename to download as: ",
284 T.input(type="text", name="filename"), " ",
285 T.input(type="submit", value="Download!"),
289 def render_view_form(self, ctx, data):
290 # this is a form where users can download files by URI, or jump to a
292 form = T.form(action="uri", method="get",
293 enctype="multipart/form-data")[
295 T.legend(class_="freeform-form-label")["View a file or directory"],
297 T.input(type="text", name="uri"), " ",
298 T.input(type="submit", value="View!"),
302 def render_upload_form(self, ctx, data):
303 # this is a form where users can upload unlinked files
304 form = T.form(action="uri", method="post",
305 enctype="multipart/form-data")[
307 T.legend(class_="freeform-form-label")["Upload a file"],
309 T.input(type="file", name="file", class_="freeform-input-file"),
310 T.input(type="hidden", name="t", value="upload"),
311 " Mutable?:", T.input(type="checkbox", name="mutable"),
312 T.input(type="submit", value="Upload!"),
316 def render_mkdir_form(self, ctx, data):
317 # this is a form where users can create new directories
318 form = T.form(action="uri", method="post",
319 enctype="multipart/form-data")[
321 T.legend(class_="freeform-form-label")["Create a directory"],
322 T.input(type="hidden", name="t", value="mkdir"),
323 T.input(type="hidden", name="redirect_to_result", value="true"),
324 T.input(type="submit", value="Create Directory!"),
328 def render_incident_button(self, ctx, data):
329 # this button triggers a foolscap-logging "incident"
330 form = T.form(action="report_incident", method="post",
331 enctype="multipart/form-data")[
333 T.legend(class_="freeform-form-label")["Report an Incident"],
334 T.input(type="hidden", name="t", value="report-incident"),
335 "What went wrong?: ",
336 T.input(type="text", name="details"), " ",
337 T.input(type="submit", value="Report!"),