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, UnhandledCapTypeError
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, UnhandledCapTypeError, 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, UnhandledCapTypeError, AssertionError):
102 # I think this can no longer be reached
103 raise WebError("'%s' is not a valid file- or directory- cap"
105 if not IFileNode.providedBy(node):
106 raise WebError("'%s' is not a file-cap" % name)
107 return filenode.FileNodeDownloadHandler(self.client, node)
109 def renderHTTP(self, ctx):
110 raise WebError("/file must be followed by a file-cap and a name",
113 class IncidentReporter(RenderMixin, rend.Page):
114 def render_POST(self, ctx):
116 log.msg(format="User reports incident through web page: %(details)s",
117 details=get_arg(req, "details", ""),
118 level=log.WEIRD, umid="LkD9Pw")
119 req.setHeader("content-type", "text/plain")
120 return "Thank you for your report!"
122 class NoReliability(rend.Page):
123 docFactory = loaders.xmlstr('''\
124 <html xmlns:n="http://nevow.com/ns/nevow/0.1">
126 <title>AllMyData - Tahoe</title>
127 <link href="/webform_css" rel="stylesheet" type="text/css"/>
128 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
131 <h2>"Reliability" page not available</h2>
132 <p>Please install the python "NumPy" module to enable this page.</p>
137 class Root(rend.Page):
140 docFactory = getxmlfile("welcome.xhtml")
142 def __init__(self, client):
143 rend.Page.__init__(self, client)
145 self.child_operations = operations.OphandleTable()
147 s = client.getServiceNamed("storage")
150 self.child_storage = storage.StorageStatus(s)
152 self.child_uri = URIHandler(client)
153 self.child_cap = URIHandler(client)
155 self.child_file = FileHandler(client)
156 self.child_named = FileHandler(client)
157 self.child_status = status.Status(client.get_history())
158 self.child_statistics = status.Statistics(client.stats_provider)
160 def child_helper_status(self, ctx):
161 # the Helper isn't attached until after the Tub starts, so this child
162 # needs to created on each request
163 return status.HelperStatus(self.client.helper)
165 child_webform_css = webform.defaultCSS
166 child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
168 child_provisioning = provisioning.ProvisioningTool()
169 if reliability.is_available():
170 child_reliability = reliability.ReliabilityTool()
172 child_reliability = NoReliability()
174 child_report_incident = IncidentReporter()
175 #child_server # let's reserve this for storage-server-over-HTTP
177 # FIXME: This code is duplicated in root.py and introweb.py.
178 def data_version(self, ctx, data):
179 return get_package_versions_string()
180 def data_import_path(self, ctx, data):
181 return str(allmydata)
182 def data_my_nodeid(self, ctx, data):
183 return idlib.nodeid_b2a(self.client.nodeid)
184 def data_my_nickname(self, ctx, data):
185 return self.client.nickname
187 def render_services(self, ctx, data):
190 ss = self.client.getServiceNamed("storage")
191 stats = ss.get_stats()
192 if stats["storage_server.accepting_immutable_shares"]:
193 msg = "accepting new shares"
195 msg = "not accepting new shares (read-only)"
196 available = stats.get("storage_server.disk_avail")
197 if available is not None:
198 msg += ", %s available" % abbreviate_size(available)
199 ul[T.li[T.a(href="storage")["Storage Server"], ": ", msg]]
201 ul[T.li["Not running storage server"]]
203 if self.client.helper:
204 stats = self.client.helper.get_stats()
205 active_uploads = stats["chk_upload_helper.active_uploads"]
206 ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
208 ul[T.li["Not running helper"]]
212 def data_introducer_furl(self, ctx, data):
213 return self.client.introducer_furl
214 def data_connected_to_introducer(self, ctx, data):
215 if self.client.connected_to_introducer():
219 def data_helper_furl(self, ctx, data):
221 uploader = self.client.getServiceNamed("uploader")
224 furl, connected = uploader.get_helper_info()
226 def data_connected_to_helper(self, ctx, data):
228 uploader = self.client.getServiceNamed("uploader")
230 return "no" # we don't even have an Uploader
231 furl, connected = uploader.get_helper_info()
236 def data_known_storage_servers(self, ctx, data):
237 sb = self.client.get_storage_broker()
238 return len(sb.get_all_serverids())
240 def data_connected_storage_servers(self, ctx, data):
241 sb = self.client.get_storage_broker()
242 return len(sb.get_all_servers())
244 def data_services(self, ctx, data):
245 sb = self.client.get_storage_broker()
246 return sb.get_all_descriptors()
248 def render_service_row(self, ctx, descriptor):
249 nodeid = descriptor.get_serverid()
251 ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
252 ctx.fillSlots("nickname", descriptor.get_nickname())
253 rhost = descriptor.get_remote_host()
255 if nodeid == self.client.nodeid:
256 rhost_s = "(loopback)"
257 elif isinstance(rhost, address.IPv4Address):
258 rhost_s = "%s:%d" % (rhost.host, rhost.port)
261 connected = "Yes: to " + rhost_s
262 since = descriptor.get_last_connect_time()
265 since = descriptor.get_last_loss_time()
266 announced = descriptor.get_announcement_time()
267 announcement = descriptor.get_announcement()
268 version = announcement["version"]
269 service_name = announcement["service-name"]
271 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
272 ctx.fillSlots("connected", connected)
273 ctx.fillSlots("connected-bool", bool(rhost))
274 ctx.fillSlots("since", time.strftime(TIME_FORMAT,
275 time.localtime(since)))
276 ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
277 time.localtime(announced)))
278 ctx.fillSlots("version", version)
279 ctx.fillSlots("service_name", service_name)
283 def render_download_form(self, ctx, data):
284 # this is a form where users can download files by URI
285 form = T.form(action="uri", method="get",
286 enctype="multipart/form-data")[
288 T.legend(class_="freeform-form-label")["Download a file"],
289 T.div["Tahoe-URI to download: ",
290 T.input(type="text", name="uri")],
291 T.div["Filename to download as: ",
292 T.input(type="text", name="filename")],
293 T.input(type="submit", value="Download!"),
297 def render_view_form(self, ctx, data):
298 # this is a form where users can download files by URI, or jump to a
300 form = T.form(action="uri", method="get",
301 enctype="multipart/form-data")[
303 T.legend(class_="freeform-form-label")["View a file or directory"],
304 "Tahoe-URI to view: ",
305 T.input(type="text", name="uri"), " ",
306 T.input(type="submit", value="View!"),
310 def render_upload_form(self, ctx, data):
311 # this is a form where users can upload unlinked files
312 form = T.form(action="uri", method="post",
313 enctype="multipart/form-data")[
315 T.legend(class_="freeform-form-label")["Upload a file"],
316 T.div["Choose a file: ",
317 T.input(type="file", name="file", class_="freeform-input-file")],
318 T.input(type="hidden", name="t", value="upload"),
319 T.div[T.input(type="checkbox", name="mutable"), T.label(for_="mutable")["Create mutable file"],
320 " ", T.input(type="submit", value="Upload!")],
324 def render_mkdir_form(self, ctx, data):
325 # this is a form where users can create new directories
326 form = T.form(action="uri", method="post",
327 enctype="multipart/form-data")[
329 T.legend(class_="freeform-form-label")["Create a directory"],
330 T.input(type="hidden", name="t", value="mkdir"),
331 T.input(type="hidden", name="redirect_to_result", value="true"),
332 T.input(type="submit", value="Create a directory"),
336 def render_incident_button(self, ctx, data):
337 # this button triggers a foolscap-logging "incident"
338 form = T.form(action="report_incident", method="post",
339 enctype="multipart/form-data")[
341 T.legend(class_="freeform-form-label")["Report an Incident"],
342 T.input(type="hidden", name="t", value="report-incident"),
343 "What went wrong?: ",
344 T.input(type="text", name="details"), " ",
345 T.input(type="submit", value="Report!"),