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
18 from allmydata.web.common import abbreviate_size, IClient, \
19 getxmlfile, WebError, get_arg, RenderMixin
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 render_GET(self, ctx):
28 uri = get_arg(req, "uri", None)
30 raise WebError("GET /uri requires uri=")
31 there = url.URL.fromContext(ctx)
32 there = there.clear("uri")
33 # I thought about escaping the childcap that we attach to the URL
34 # here, but it seems that nevow does that for us.
35 there = there.child(uri)
38 def render_PUT(self, ctx):
40 # either "PUT /uri" to create an unlinked file, or
41 # "PUT /uri?t=mkdir" to create an unlinked directory
42 t = get_arg(req, "t", "").strip()
44 mutable = bool(get_arg(req, "mutable", "").strip())
46 return unlinked.PUTUnlinkedSSK(ctx)
48 return unlinked.PUTUnlinkedCHK(ctx)
50 return unlinked.PUTUnlinkedCreateDirectory(ctx)
51 errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
53 raise WebError(errmsg, http.BAD_REQUEST)
55 def render_POST(self, ctx):
56 # "POST /uri?t=upload&file=newfile" to upload an
57 # unlinked file or "POST /uri?t=mkdir" to create a
60 t = get_arg(req, "t", "").strip()
61 if t in ("", "upload"):
62 mutable = bool(get_arg(req, "mutable", "").strip())
64 return unlinked.POSTUnlinkedSSK(ctx)
66 return unlinked.POSTUnlinkedCHK(ctx)
68 return unlinked.POSTUnlinkedCreateDirectory(ctx)
69 errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
71 raise WebError(errmsg, http.BAD_REQUEST)
73 def childFactory(self, ctx, name):
74 # 'name' is expected to be a URI
77 node = client.create_node_from_uri(name)
78 return directory.make_handler_for(node)
79 except (TypeError, AssertionError):
80 raise WebError("'%s' is not a valid file- or directory- cap"
83 class FileHandler(rend.Page):
84 # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
85 # file can be downloaded correctly by tools like "wget".
87 def childFactory(self, ctx, name):
89 if req.method not in ("GET", "HEAD"):
90 raise WebError("/file can only be used with GET or HEAD")
91 # 'name' must be a file URI
94 node = client.create_node_from_uri(name)
95 except (TypeError, AssertionError):
96 raise WebError("'%s' is not a valid file- or directory- cap"
98 if not IFileNode.providedBy(node):
99 raise WebError("'%s' is not a file-cap" % name)
100 return filenode.FileNodeDownloadHandler(node)
102 def renderHTTP(self, ctx):
103 raise WebError("/file must be followed by a file-cap and a name",
106 class IncidentReporter(RenderMixin, rend.Page):
107 def render_POST(self, ctx):
109 log.msg(format="User reports incident through web page: %(details)s",
110 details=get_arg(req, "details", ""),
111 level=log.WEIRD, umid="LkD9Pw")
112 req.setHeader("content-type", "text/plain")
113 return "Thank you for your report!"
115 class NoReliability(rend.Page):
116 docFactory = loaders.xmlstr('''\
117 <html xmlns:n="http://nevow.com/ns/nevow/0.1">
119 <title>AllMyData - Tahoe</title>
120 <link href="/webform_css" rel="stylesheet" type="text/css"/>
121 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
124 <h2>"Reliability" page not available</h2>
125 <p>Please install the python "Numeric" module to enable this page.</p>
130 class Root(rend.Page):
133 docFactory = getxmlfile("welcome.xhtml")
135 def __init__(self, original=None):
136 rend.Page.__init__(self, original)
137 self.child_operations = operations.OphandleTable()
139 child_uri = URIHandler()
140 child_cap = URIHandler()
141 child_file = FileHandler()
142 child_named = FileHandler()
144 child_webform_css = webform.defaultCSS
145 child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
147 child_provisioning = provisioning.ProvisioningTool()
148 if reliability.is_available():
149 child_reliability = reliability.ReliabilityTool()
151 child_reliability = NoReliability()
152 child_status = status.Status()
153 child_helper_status = status.HelperStatus()
154 child_statistics = status.Statistics()
156 child_report_incident = IncidentReporter()
158 def data_version(self, ctx, data):
159 return get_package_versions_string()
160 def data_import_path(self, ctx, data):
161 return str(allmydata)
162 def data_my_nodeid(self, ctx, data):
163 return idlib.nodeid_b2a(IClient(ctx).nodeid)
164 def data_my_nickname(self, ctx, data):
165 return IClient(ctx).nickname
167 def render_services(self, ctx, data):
169 client = IClient(ctx)
171 ss = client.getServiceNamed("storage")
172 allocated_s = abbreviate_size(ss.allocated_size())
173 allocated = "about %s allocated" % allocated_s
174 reserved = "%s reserved" % abbreviate_size(ss.reserved_space)
175 ul[T.li["Storage Server: %s, %s" % (allocated, reserved)]]
177 ul[T.li["Not running storage server"]]
180 h = client.getServiceNamed("helper")
181 stats = h.get_stats()
182 active_uploads = stats["chk_upload_helper.active_uploads"]
183 ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
185 ul[T.li["Not running helper"]]
189 def data_introducer_furl(self, ctx, data):
190 return IClient(ctx).introducer_furl
191 def data_connected_to_introducer(self, ctx, data):
192 if IClient(ctx).connected_to_introducer():
196 def data_helper_furl(self, ctx, data):
198 uploader = IClient(ctx).getServiceNamed("uploader")
201 furl, connected = uploader.get_helper_info()
203 def data_connected_to_helper(self, ctx, data):
205 uploader = IClient(ctx).getServiceNamed("uploader")
207 return "no" # we don't even have an Uploader
208 furl, connected = uploader.get_helper_info()
213 def data_known_storage_servers(self, ctx, data):
214 ic = IClient(ctx).introducer_client
216 for c in ic.get_all_connectors().values()
217 if c.service_name == "storage"]
220 def data_connected_storage_servers(self, ctx, data):
221 ic = IClient(ctx).introducer_client
222 return len(ic.get_all_connections_for("storage"))
224 def data_services(self, ctx, data):
225 ic = IClient(ctx).introducer_client
226 c = [ (service_name, nodeid, rsc)
227 for (nodeid, service_name), rsc
228 in ic.get_all_connectors().items() ]
232 def render_service_row(self, ctx, data):
233 (service_name, nodeid, rsc) = data
234 ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
235 ctx.fillSlots("nickname", rsc.nickname)
237 rhost = rsc.remote_host
238 if nodeid == IClient(ctx).nodeid:
239 rhost_s = "(loopback)"
240 elif isinstance(rhost, address.IPv4Address):
241 rhost_s = "%s:%d" % (rhost.host, rhost.port)
244 connected = "Yes: to " + rhost_s
245 since = rsc.last_connect_time
248 since = rsc.last_loss_time
250 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
251 ctx.fillSlots("connected", connected)
252 ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
253 ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
254 time.localtime(rsc.announcement_time)))
255 ctx.fillSlots("version", rsc.version)
256 ctx.fillSlots("service_name", rsc.service_name)
260 def render_download_form(self, ctx, data):
261 # this is a form where users can download files by URI
262 form = T.form(action="uri", method="get",
263 enctype="multipart/form-data")[
265 T.legend(class_="freeform-form-label")["Download a file"],
267 T.input(type="text", name="uri"), " ",
268 "Filename to download as: ",
269 T.input(type="text", name="filename"), " ",
270 T.input(type="submit", value="Download!"),
274 def render_view_form(self, ctx, data):
275 # this is a form where users can download files by URI, or jump to a
277 form = T.form(action="uri", method="get",
278 enctype="multipart/form-data")[
280 T.legend(class_="freeform-form-label")["View a file or directory"],
282 T.input(type="text", name="uri"), " ",
283 T.input(type="submit", value="View!"),
287 def render_upload_form(self, ctx, data):
288 # this is a form where users can upload unlinked files
289 form = T.form(action="uri", method="post",
290 enctype="multipart/form-data")[
292 T.legend(class_="freeform-form-label")["Upload a file"],
294 T.input(type="file", name="file", class_="freeform-input-file"),
295 T.input(type="hidden", name="t", value="upload"),
296 " Mutable?:", T.input(type="checkbox", name="mutable"),
297 T.input(type="submit", value="Upload!"),
301 def render_mkdir_form(self, ctx, data):
302 # this is a form where users can create new directories
303 form = T.form(action="uri", method="post",
304 enctype="multipart/form-data")[
306 T.legend(class_="freeform-form-label")["Create a directory"],
307 T.input(type="hidden", name="t", value="mkdir"),
308 T.input(type="hidden", name="redirect_to_result", value="true"),
309 T.input(type="submit", value="Create Directory!"),
313 def render_incident_button(self, ctx, data):
314 # this button triggers a foolscap-logging "incident"
315 form = T.form(action="report_incident", method="post",
316 enctype="multipart/form-data")[
318 T.legend(class_="freeform-form-label")["Report an Incident"],
319 T.input(type="hidden", name="t", value="report-incident"),
320 "What went wrong?: ",
321 T.input(type="text", name="details"), " ",
322 T.input(type="submit", value="Report!"),