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
164 helper = self.client.getServiceNamed("helper")
167 return status.HelperStatus(helper)
169 child_webform_css = webform.defaultCSS
170 child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
172 child_provisioning = provisioning.ProvisioningTool()
173 if reliability.is_available():
174 child_reliability = reliability.ReliabilityTool()
176 child_reliability = NoReliability()
178 child_report_incident = IncidentReporter()
179 #child_server # let's reserve this for storage-server-over-HTTP
181 # FIXME: This code is duplicated in root.py and introweb.py.
182 def data_version(self, ctx, data):
183 return get_package_versions_string()
184 def data_import_path(self, ctx, data):
185 return str(allmydata)
186 def data_my_nodeid(self, ctx, data):
187 return idlib.nodeid_b2a(self.client.nodeid)
188 def data_my_nickname(self, ctx, data):
189 return self.client.nickname
191 def render_services(self, ctx, data):
194 ss = self.client.getServiceNamed("storage")
195 stats = ss.get_stats()
196 if stats["storage_server.accepting_immutable_shares"]:
197 msg = "accepting new shares"
199 msg = "not accepting new shares (read-only)"
200 available = stats.get("storage_server.disk_avail")
201 if available is not None:
202 msg += ", %s available" % abbreviate_size(available)
203 ul[T.li[T.a(href="storage")["Storage Server"], ": ", msg]]
205 ul[T.li["Not running storage server"]]
208 h = self.client.getServiceNamed("helper")
209 stats = h.get_stats()
210 active_uploads = stats["chk_upload_helper.active_uploads"]
211 ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
213 ul[T.li["Not running helper"]]
217 def data_introducer_furl(self, ctx, data):
218 return self.client.introducer_furl
219 def data_connected_to_introducer(self, ctx, data):
220 if self.client.connected_to_introducer():
224 def data_helper_furl(self, ctx, data):
226 uploader = self.client.getServiceNamed("uploader")
229 furl, connected = uploader.get_helper_info()
231 def data_connected_to_helper(self, ctx, data):
233 uploader = self.client.getServiceNamed("uploader")
235 return "no" # we don't even have an Uploader
236 furl, connected = uploader.get_helper_info()
241 def data_known_storage_servers(self, ctx, data):
242 sb = self.client.get_storage_broker()
243 return len(sb.get_all_serverids())
245 def data_connected_storage_servers(self, ctx, data):
246 sb = self.client.get_storage_broker()
247 return len(sb.get_all_servers())
249 def data_services(self, ctx, data):
250 sb = self.client.get_storage_broker()
251 return sb.get_all_descriptors()
253 def render_service_row(self, ctx, descriptor):
254 nodeid = descriptor.get_serverid()
256 ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
257 ctx.fillSlots("nickname", descriptor.get_nickname())
258 rhost = descriptor.get_remote_host()
260 if nodeid == self.client.nodeid:
261 rhost_s = "(loopback)"
262 elif isinstance(rhost, address.IPv4Address):
263 rhost_s = "%s:%d" % (rhost.host, rhost.port)
266 connected = "Yes: to " + rhost_s
267 since = descriptor.get_last_connect_time()
270 since = descriptor.get_last_loss_time()
271 announced = descriptor.get_announcement_time()
272 announcement = descriptor.get_announcement()
273 version = announcement["version"]
274 service_name = announcement["service-name"]
276 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
277 ctx.fillSlots("connected", connected)
278 ctx.fillSlots("connected-bool", bool(rhost))
279 ctx.fillSlots("since", time.strftime(TIME_FORMAT,
280 time.localtime(since)))
281 ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
282 time.localtime(announced)))
283 ctx.fillSlots("version", version)
284 ctx.fillSlots("service_name", 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!"),