4 from twisted.internet import address
5 from twisted.web import http
6 from nevow import rend, url, tags as T
7 from nevow.inevow import IRequest
8 from nevow.static import File as nevow_File # TODO: merge with static.File?
9 from nevow.util import resource_filename
10 from formless import webform
12 import allmydata # to display import path
13 from allmydata import get_package_versions_string
14 from allmydata import provisioning
15 from allmydata.util import idlib, log
16 from allmydata.interfaces import IFileNode
17 from allmydata.web import filenode, directory, unlinked, status, operations
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 setAmbientUploadAuthority(self, ambientUploadAuthority):
27 self.ambientUploadAuthority = ambientUploadAuthority
29 def render_GET(self, ctx):
31 uri = get_arg(req, "uri", None)
33 raise WebError("GET /uri requires uri=")
34 there = url.URL.fromContext(ctx)
35 there = there.clear("uri")
36 # I thought about escaping the childcap that we attach to the URL
37 # here, but it seems that nevow does that for us.
38 there = there.child(uri)
41 def render_PUT(self, ctx):
42 if not self.ambientUploadAuthority:
43 raise WebError("/uri handling of PUT not enabled on this node")
46 # either "PUT /uri" to create an unlinked file, or
47 # "PUT /uri?t=mkdir" to create an unlinked directory
48 t = get_arg(req, "t", "").strip()
50 mutable = bool(get_arg(req, "mutable", "").strip())
52 return unlinked.PUTUnlinkedSSK(ctx)
54 return unlinked.PUTUnlinkedCHK(ctx)
56 return unlinked.PUTUnlinkedCreateDirectory(ctx)
57 errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
59 raise WebError(errmsg, http.BAD_REQUEST)
61 def render_POST(self, ctx):
62 if not self.ambientUploadAuthority:
63 raise WebError("/uri handling of POST not enabled on this node")
65 # "POST /uri?t=upload&file=newfile" to upload an
66 # unlinked file or "POST /uri?t=mkdir" to create a
69 t = get_arg(req, "t", "").strip()
70 if t in ("", "upload"):
71 mutable = bool(get_arg(req, "mutable", "").strip())
73 return unlinked.POSTUnlinkedSSK(ctx)
75 return unlinked.POSTUnlinkedCHK(ctx)
77 return unlinked.POSTUnlinkedCreateDirectory(ctx)
78 errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
80 raise WebError(errmsg, http.BAD_REQUEST)
82 def childFactory(self, ctx, name):
83 # 'name' is expected to be a URI
86 node = client.create_node_from_uri(name)
87 return directory.make_handler_for(node)
88 except (TypeError, AssertionError):
89 raise WebError("'%s' is not a valid file- or directory- cap"
92 class FileHandler(rend.Page):
93 # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
94 # file can be downloaded correctly by tools like "wget".
96 def childFactory(self, ctx, name):
98 if req.method not in ("GET", "HEAD"):
99 raise WebError("/file can only be used with GET or HEAD")
100 # 'name' must be a file URI
101 client = IClient(ctx)
103 node = client.create_node_from_uri(name)
104 except (TypeError, AssertionError):
105 raise WebError("'%s' is not a valid file- or directory- cap"
107 if not IFileNode.providedBy(node):
108 raise WebError("'%s' is not a file-cap" % name)
109 return filenode.FileNodeDownloadHandler(node)
111 def renderHTTP(self, ctx):
112 raise WebError("/file must be followed by a file-cap and a name",
115 class IncidentReporter(RenderMixin, rend.Page):
116 def render_POST(self, ctx):
118 log.msg(format="User reports incident through web page: %(details)s",
119 details=get_arg(req, "details", ""),
120 level=log.WEIRD, umid="LkD9Pw")
121 req.setHeader("content-type", "text/plain")
122 return "Thank you for your report!"
125 class Root(rend.Page):
128 docFactory = getxmlfile("welcome.xhtml")
130 def __init__(self, original=None):
131 rend.Page.__init__(self, original)
132 self.child_operations = operations.OphandleTable()
134 def setAmbientUploadAuthority(self, ambientUploadAuthority):
135 self.child_uri.setAmbientUploadAuthority(ambientUploadAuthority)
137 child_uri = URIHandler()
138 child_cap = URIHandler()
139 child_file = FileHandler()
140 child_named = FileHandler()
142 child_webform_css = webform.defaultCSS
143 child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
145 child_provisioning = provisioning.ProvisioningTool()
146 child_status = status.Status()
147 child_helper_status = status.HelperStatus()
148 child_statistics = status.Statistics()
150 child_report_incident = IncidentReporter()
152 def data_version(self, ctx, data):
153 return get_package_versions_string()
154 def data_import_path(self, ctx, data):
155 return str(allmydata)
156 def data_my_nodeid(self, ctx, data):
157 return idlib.nodeid_b2a(IClient(ctx).nodeid)
158 def data_my_nickname(self, ctx, data):
159 return IClient(ctx).nickname
161 def render_services(self, ctx, data):
163 client = IClient(ctx)
165 ss = client.getServiceNamed("storage")
166 allocated_s = abbreviate_size(ss.allocated_size())
167 allocated = "about %s allocated" % allocated_s
168 reserved = "%s reserved" % abbreviate_size(ss.reserved_space)
169 ul[T.li["Storage Server: %s, %s" % (allocated, reserved)]]
171 ul[T.li["Not running storage server"]]
174 h = client.getServiceNamed("helper")
175 stats = h.get_stats()
176 active_uploads = stats["chk_upload_helper.active_uploads"]
177 ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
179 ul[T.li["Not running helper"]]
183 def data_introducer_furl(self, ctx, data):
184 return IClient(ctx).introducer_furl
185 def data_connected_to_introducer(self, ctx, data):
186 if IClient(ctx).connected_to_introducer():
190 def data_helper_furl(self, ctx, data):
192 uploader = IClient(ctx).getServiceNamed("uploader")
195 furl, connected = uploader.get_helper_info()
197 def data_connected_to_helper(self, ctx, data):
199 uploader = IClient(ctx).getServiceNamed("uploader")
201 return "no" # we don't even have an Uploader
202 furl, connected = uploader.get_helper_info()
207 def data_known_storage_servers(self, ctx, data):
208 ic = IClient(ctx).introducer_client
210 for c in ic.get_all_connectors().values()
211 if c.service_name == "storage"]
214 def data_connected_storage_servers(self, ctx, data):
215 ic = IClient(ctx).introducer_client
216 return len(ic.get_all_connections_for("storage"))
218 def data_services(self, ctx, data):
219 ic = IClient(ctx).introducer_client
220 c = [ (service_name, nodeid, rsc)
221 for (nodeid, service_name), rsc
222 in ic.get_all_connectors().items() ]
226 def render_service_row(self, ctx, data):
227 (service_name, nodeid, rsc) = data
228 ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
229 ctx.fillSlots("nickname", rsc.nickname)
231 rhost = rsc.remote_host
232 if nodeid == IClient(ctx).nodeid:
233 rhost_s = "(loopback)"
234 elif isinstance(rhost, address.IPv4Address):
235 rhost_s = "%s:%d" % (rhost.host, rhost.port)
238 connected = "Yes: to " + rhost_s
239 since = rsc.last_connect_time
242 since = rsc.last_loss_time
244 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
245 ctx.fillSlots("connected", connected)
246 ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
247 ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
248 time.localtime(rsc.announcement_time)))
249 ctx.fillSlots("version", rsc.version)
250 ctx.fillSlots("service_name", rsc.service_name)
254 def render_download_form(self, ctx, data):
255 # this is a form where users can download files by URI
256 form = T.form(action="uri", method="get",
257 enctype="multipart/form-data")[
259 T.legend(class_="freeform-form-label")["Download a file"],
261 T.input(type="text", name="uri"), " ",
262 "Filename to download as: ",
263 T.input(type="text", name="filename"), " ",
264 T.input(type="submit", value="Download!"),
268 def render_view_form(self, ctx, data):
269 # this is a form where users can download files by URI, or jump to a
271 form = T.form(action="uri", method="get",
272 enctype="multipart/form-data")[
274 T.legend(class_="freeform-form-label")["View a file or directory"],
276 T.input(type="text", name="uri"), " ",
277 T.input(type="submit", value="View!"),
281 def render_upload_form(self, ctx, data):
282 # this is a form where users can upload unlinked files
283 form = T.form(action="uri", method="post",
284 enctype="multipart/form-data")[
286 T.legend(class_="freeform-form-label")["Upload a file"],
288 T.input(type="file", name="file", class_="freeform-input-file"),
289 T.input(type="hidden", name="t", value="upload"),
290 " Mutable?:", T.input(type="checkbox", name="mutable"),
291 T.input(type="submit", value="Upload!"),
295 def render_mkdir_form(self, ctx, data):
296 # this is a form where users can create new directories
297 form = T.form(action="uri", method="post",
298 enctype="multipart/form-data")[
300 T.legend(class_="freeform-form-label")["Create a directory"],
301 T.input(type="hidden", name="t", value="mkdir"),
302 T.input(type="hidden", name="redirect_to_result", value="true"),
303 T.input(type="submit", value="Create Directory!"),
307 def render_incident_button(self, ctx, data):
308 # this button triggers a foolscap-logging "incident"
309 form = T.form(action="report_incident", method="post",
310 enctype="multipart/form-data")[
312 T.legend(class_="freeform-form-label")["Report an Incident"],
313 T.input(type="hidden", name="t", value="report-incident"),
314 "What went wrong?: ",
315 T.input(type="text", name="details"), " ",
316 T.input(type="submit", value="Report!"),