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, get_format, get_mutable_type
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 file_format = get_format(req, "CHK")
49 mutable_type = get_mutable_type(file_format)
50 if mutable_type is not None:
51 return unlinked.PUTUnlinkedSSK(req, self.client, mutable_type)
53 return unlinked.PUTUnlinkedCHK(req, self.client)
55 return unlinked.PUTUnlinkedCreateDirectory(req, self.client)
56 errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
58 raise WebError(errmsg, http.BAD_REQUEST)
60 def render_POST(self, ctx):
61 # "POST /uri?t=upload&file=newfile" to upload an
62 # unlinked file or "POST /uri?t=mkdir" to create a
65 t = get_arg(req, "t", "").strip()
66 if t in ("", "upload"):
67 file_format = get_format(req)
68 mutable_type = get_mutable_type(file_format)
69 if mutable_type is not None:
70 return unlinked.POSTUnlinkedSSK(req, self.client, mutable_type)
72 return unlinked.POSTUnlinkedCHK(req, self.client)
74 return unlinked.POSTUnlinkedCreateDirectory(req, self.client)
75 elif t == "mkdir-with-children":
76 return unlinked.POSTUnlinkedCreateDirectoryWithChildren(req,
78 elif t == "mkdir-immutable":
79 return unlinked.POSTUnlinkedCreateImmutableDirectory(req,
81 errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
83 raise WebError(errmsg, http.BAD_REQUEST)
85 def childFactory(self, ctx, name):
86 # 'name' is expected to be a URI
88 node = self.client.create_node_from_uri(name)
89 return directory.make_handler_for(node, self.client)
90 except (TypeError, AssertionError):
91 raise WebError("'%s' is not a valid file- or directory- cap"
94 class FileHandler(rend.Page):
95 # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
96 # file can be downloaded correctly by tools like "wget".
98 def __init__(self, client):
99 rend.Page.__init__(self, client)
102 def childFactory(self, ctx, name):
104 if req.method not in ("GET", "HEAD"):
105 raise WebError("/file can only be used with GET or HEAD")
106 # 'name' must be a file URI
108 node = self.client.create_node_from_uri(name)
109 except (TypeError, AssertionError):
110 # I think this can no longer be reached
111 raise WebError("'%s' is not a valid file- or directory- cap"
113 if not IFileNode.providedBy(node):
114 raise WebError("'%s' is not a file-cap" % name)
115 return filenode.FileNodeDownloadHandler(self.client, node)
117 def renderHTTP(self, ctx):
118 raise WebError("/file must be followed by a file-cap and a name",
121 class IncidentReporter(RenderMixin, rend.Page):
122 def render_POST(self, ctx):
124 log.msg(format="User reports incident through web page: %(details)s",
125 details=get_arg(req, "details", ""),
126 level=log.WEIRD, umid="LkD9Pw")
127 req.setHeader("content-type", "text/plain")
128 return "Thank you for your report!"
130 class NoReliability(rend.Page):
131 docFactory = loaders.xmlstr('''\
132 <html xmlns:n="http://nevow.com/ns/nevow/0.1">
134 <title>AllMyData - Tahoe</title>
135 <link href="/webform_css" rel="stylesheet" type="text/css"/>
136 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
139 <h2>"Reliability" page not available</h2>
140 <p>Please install the python "NumPy" module to enable this page.</p>
147 class Root(rend.Page):
150 docFactory = getxmlfile("welcome.xhtml")
152 def __init__(self, client, clock=None):
153 rend.Page.__init__(self, client)
155 # If set, clock is a twisted.internet.task.Clock that the tests
156 # use to test ophandle expiration.
157 self.child_operations = operations.OphandleTable(clock)
159 s = client.getServiceNamed("storage")
162 self.child_storage = storage.StorageStatus(s)
164 self.child_uri = URIHandler(client)
165 self.child_cap = URIHandler(client)
167 self.child_file = FileHandler(client)
168 self.child_named = FileHandler(client)
169 self.child_status = status.Status(client.get_history())
170 self.child_statistics = status.Statistics(client.stats_provider)
171 static_dir = resource_filename("allmydata.web", "static")
172 for filen in os.listdir(static_dir):
173 self.putChild(filen, nevow_File(os.path.join(static_dir, filen)))
175 def child_helper_status(self, ctx):
176 # the Helper isn't attached until after the Tub starts, so this child
177 # needs to created on each request
178 return status.HelperStatus(self.client.helper)
180 child_provisioning = provisioning.ProvisioningTool()
181 if reliability.is_available():
182 child_reliability = reliability.ReliabilityTool()
184 child_reliability = NoReliability()
186 child_report_incident = IncidentReporter()
187 #child_server # let's reserve this for storage-server-over-HTTP
189 # FIXME: This code is duplicated in root.py and introweb.py.
190 def data_version(self, ctx, data):
191 return get_package_versions_string()
192 def data_import_path(self, ctx, data):
193 return str(allmydata)
194 def data_my_nodeid(self, ctx, data):
195 return idlib.nodeid_b2a(self.client.nodeid)
196 def data_my_nickname(self, ctx, data):
197 return self.client.nickname
199 def render_services(self, ctx, data):
202 ss = self.client.getServiceNamed("storage")
203 stats = ss.get_stats()
204 if stats["storage_server.accepting_immutable_shares"]:
205 msg = "accepting new shares"
207 msg = "not accepting new shares (read-only)"
208 available = stats.get("storage_server.disk_avail")
209 if available is not None:
210 msg += ", %s available" % abbreviate_size(available)
211 ul[T.li[T.a(href="storage")["Storage Server"], ": ", msg]]
213 ul[T.li["Not running storage server"]]
215 if self.client.helper:
216 stats = self.client.helper.get_stats()
217 active_uploads = stats["chk_upload_helper.active_uploads"]
218 ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
220 ul[T.li["Not running helper"]]
224 def data_introducer_furl(self, ctx, data):
225 return self.client.introducer_furl
226 def data_connected_to_introducer(self, ctx, data):
227 if self.client.connected_to_introducer():
231 def data_helper_furl(self, ctx, data):
233 uploader = self.client.getServiceNamed("uploader")
236 furl, connected = uploader.get_helper_info()
238 def data_connected_to_helper(self, ctx, data):
240 uploader = self.client.getServiceNamed("uploader")
242 return "no" # we don't even have an Uploader
243 furl, connected = uploader.get_helper_info()
248 def data_known_storage_servers(self, ctx, data):
249 sb = self.client.get_storage_broker()
250 return len(sb.get_all_serverids())
252 def data_connected_storage_servers(self, ctx, data):
253 sb = self.client.get_storage_broker()
254 return len(sb.get_connected_servers())
256 def data_services(self, ctx, data):
257 sb = self.client.get_storage_broker()
258 return sorted(sb.get_known_servers(), key=lambda s: s.get_serverid())
260 def render_service_row(self, ctx, server):
261 nodeid = server.get_serverid()
263 ctx.fillSlots("peerid", server.get_longname())
264 ctx.fillSlots("nickname", server.get_nickname())
265 rhost = server.get_remote_host()
267 if nodeid == self.client.nodeid:
268 rhost_s = "(loopback)"
269 elif isinstance(rhost, address.IPv4Address):
270 rhost_s = "%s:%d" % (rhost.host, rhost.port)
273 connected = "Yes: to " + rhost_s
274 since = server.get_last_connect_time()
277 since = server.get_last_loss_time()
278 announced = server.get_announcement_time()
279 announcement = server.get_announcement()
280 version = announcement["my-version"]
281 service_name = announcement["service-name"]
283 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
284 ctx.fillSlots("connected", connected)
285 ctx.fillSlots("connected-bool", bool(rhost))
286 ctx.fillSlots("since", time.strftime(TIME_FORMAT,
287 time.localtime(since)))
288 ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
289 time.localtime(announced)))
290 ctx.fillSlots("version", version)
291 ctx.fillSlots("service_name", service_name)
295 def render_download_form(self, ctx, data):
296 # this is a form where users can download files by URI
297 form = T.form(action="uri", method="get",
298 enctype="multipart/form-data")[
300 T.legend(class_="freeform-form-label")["Download a file"],
301 T.div["Tahoe-URI to download:"+SPACE,
302 T.input(type="text", name="uri")],
303 T.div["Filename to download as:"+SPACE,
304 T.input(type="text", name="filename")],
305 T.input(type="submit", value="Download!"),
309 def render_view_form(self, ctx, data):
310 # this is a form where users can download files by URI, or jump to a
312 form = T.form(action="uri", method="get",
313 enctype="multipart/form-data")[
315 T.legend(class_="freeform-form-label")["View a file or directory"],
316 "Tahoe-URI to view:"+SPACE,
317 T.input(type="text", name="uri"), SPACE*2,
318 T.input(type="submit", value="View!"),
322 def render_upload_form(self, ctx, data):
323 # This is a form where users can upload unlinked files.
324 # Users can choose immutable, SDMF, or MDMF from a radio button.
326 upload_chk = T.input(type='radio', name='format',
327 value='chk', id='upload-chk',
329 upload_sdmf = T.input(type='radio', name='format',
330 value='sdmf', id='upload-sdmf')
331 upload_mdmf = T.input(type='radio', name='format',
332 value='mdmf', id='upload-mdmf')
334 form = T.form(action="uri", method="post",
335 enctype="multipart/form-data")[
337 T.legend(class_="freeform-form-label")["Upload a file"],
338 T.div["Choose a file:"+SPACE,
339 T.input(type="file", name="file", class_="freeform-input-file")],
340 T.input(type="hidden", name="t", value="upload"),
341 T.div[upload_chk, T.label(for_="upload-chk") [" Immutable"], SPACE,
342 upload_sdmf, T.label(for_="upload-sdmf")[" SDMF"], SPACE,
343 upload_mdmf, T.label(for_="upload-mdmf")[" MDMF (experimental)"], SPACE*2,
344 T.input(type="submit", value="Upload!")],
348 def render_mkdir_form(self, ctx, data):
349 # This is a form where users can create new directories.
350 # Users can choose SDMF or MDMF from a radio button.
352 mkdir_sdmf = T.input(type='radio', name='format',
353 value='sdmf', id='mkdir-sdmf',
355 mkdir_mdmf = T.input(type='radio', name='format',
356 value='mdmf', id='mkdir-mdmf')
358 form = T.form(action="uri", method="post",
359 enctype="multipart/form-data")[
361 T.legend(class_="freeform-form-label")["Create a directory"],
362 mkdir_sdmf, T.label(for_='mkdir-sdmf')[" SDMF"], SPACE,
363 mkdir_mdmf, T.label(for_='mkdir-mdmf')[" MDMF (experimental)"], SPACE*2,
364 T.input(type="hidden", name="t", value="mkdir"),
365 T.input(type="hidden", name="redirect_to_result", value="true"),
366 T.input(type="submit", value="Create a directory"),
370 def render_incident_button(self, ctx, data):
371 # this button triggers a foolscap-logging "incident"
372 form = T.form(action="report_incident", method="post",
373 enctype="multipart/form-data")[
375 T.legend(class_="freeform-form-label")["Report an Incident"],
376 T.input(type="hidden", name="t", value="report-incident"),
377 "What went wrong?:"+SPACE,
378 T.input(type="text", name="details"), SPACE,
379 T.input(type="submit", value="Report!"),