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
18 from allmydata.web.common import abbreviate_size, IClient, getxmlfile, \
19 WebError, get_arg, RenderMixin
23 class URIHandler(RenderMixin, rend.Page):
24 # I live at /uri . There are several operations defined on /uri itself,
25 # mostly involved with creation of unlinked files and directories.
27 def render_GET(self, ctx):
29 uri = get_arg(req, "uri", None)
31 raise WebError("GET /uri requires uri=")
32 there = url.URL.fromContext(ctx)
33 there = there.clear("uri")
34 # I thought about escaping the childcap that we attach to the URL
35 # here, but it seems that nevow does that for us.
36 there = there.child(uri)
39 def render_PUT(self, ctx):
41 # either "PUT /uri" to create an unlinked file, or
42 # "PUT /uri?t=mkdir" to create an unlinked directory
43 t = get_arg(req, "t", "").strip()
45 mutable = bool(get_arg(req, "mutable", "").strip())
47 return unlinked.PUTUnlinkedSSK(ctx)
49 return unlinked.PUTUnlinkedCHK(ctx)
51 return unlinked.PUTUnlinkedCreateDirectory(ctx)
52 errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
54 raise WebError(errmsg, http.BAD_REQUEST)
56 def render_POST(self, ctx):
57 # "POST /uri?t=upload&file=newfile" to upload an
58 # unlinked file or "POST /uri?t=mkdir" to create a
61 t = get_arg(req, "t", "").strip()
62 if t in ("", "upload"):
63 mutable = bool(get_arg(req, "mutable", "").strip())
65 return unlinked.POSTUnlinkedSSK(ctx)
67 return unlinked.POSTUnlinkedCHK(ctx)
69 return unlinked.POSTUnlinkedCreateDirectory(ctx)
70 errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
72 raise WebError(errmsg, http.BAD_REQUEST)
74 def childFactory(self, ctx, name):
75 # 'name' is expected to be a URI
78 node = client.create_node_from_uri(name)
79 return directory.make_handler_for(node)
80 except (TypeError, AssertionError):
81 raise WebError("'%s' is not a valid file- or directory- cap"
84 class FileHandler(rend.Page):
85 # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
86 # file can be downloaded correctly by tools like "wget".
88 def childFactory(self, ctx, name):
90 if req.method not in ("GET", "HEAD"):
91 raise WebError("/file can only be used with GET or HEAD")
92 # 'name' must be a file URI
95 node = client.create_node_from_uri(name)
96 except (TypeError, AssertionError):
97 raise WebError("'%s' is not a valid file- or directory- cap"
99 if not IFileNode.providedBy(node):
100 raise WebError("'%s' is not a file-cap" % name)
101 return filenode.FileNodeDownloadHandler(node)
103 def renderHTTP(self, ctx):
104 raise WebError("/file must be followed by a file-cap and a name",
107 class IncidentReporter(RenderMixin, rend.Page):
108 def render_POST(self, ctx):
110 log.msg(format="User reports incident through web page: %(details)s",
111 details=get_arg(req, "details", ""),
112 level=log.WEIRD, umid="LkD9Pw")
113 req.setHeader("content-type", "text/plain")
114 return "Thank you for your report!"
116 class Root(rend.Page):
119 docFactory = getxmlfile("welcome.xhtml")
121 child_uri = URIHandler()
122 child_cap = URIHandler()
123 child_file = FileHandler()
124 child_named = FileHandler()
126 child_webform_css = webform.defaultCSS
127 child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
129 child_provisioning = provisioning.ProvisioningTool()
130 child_status = status.Status()
131 child_helper_status = status.HelperStatus()
132 child_statistics = status.Statistics()
134 child_report_incident = IncidentReporter()
136 def data_version(self, ctx, data):
137 return get_package_versions_string()
138 def data_import_path(self, ctx, data):
139 return str(allmydata)
140 def data_my_nodeid(self, ctx, data):
141 return idlib.nodeid_b2a(IClient(ctx).nodeid)
142 def data_my_nickname(self, ctx, data):
143 return IClient(ctx).nickname
145 def render_services(self, ctx, data):
147 client = IClient(ctx)
149 ss = client.getServiceNamed("storage")
150 allocated_s = abbreviate_size(ss.allocated_size())
151 allocated = "about %s allocated" % allocated_s
152 sizelimit = "no size limit"
153 if ss.sizelimit is not None:
154 sizelimit = "size limit is %s" % abbreviate_size(ss.sizelimit)
155 ul[T.li["Storage Server: %s, %s" % (allocated, sizelimit)]]
157 ul[T.li["Not running storage server"]]
160 h = client.getServiceNamed("helper")
161 stats = h.get_stats()
162 active_uploads = stats["chk_upload_helper.active_uploads"]
163 ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
165 ul[T.li["Not running helper"]]
169 def data_introducer_furl(self, ctx, data):
170 return IClient(ctx).introducer_furl
171 def data_connected_to_introducer(self, ctx, data):
172 if IClient(ctx).connected_to_introducer():
176 def data_helper_furl(self, ctx, data):
178 uploader = IClient(ctx).getServiceNamed("uploader")
181 furl, connected = uploader.get_helper_info()
183 def data_connected_to_helper(self, ctx, data):
185 uploader = IClient(ctx).getServiceNamed("uploader")
187 return "no" # we don't even have an Uploader
188 furl, connected = uploader.get_helper_info()
193 def data_known_storage_servers(self, ctx, data):
194 ic = IClient(ctx).introducer_client
196 for c in ic.get_all_connectors().values()
197 if c.service_name == "storage"]
200 def data_connected_storage_servers(self, ctx, data):
201 ic = IClient(ctx).introducer_client
202 return len(ic.get_all_connections_for("storage"))
204 def data_services(self, ctx, data):
205 ic = IClient(ctx).introducer_client
206 c = [ (service_name, nodeid, rsc)
207 for (nodeid, service_name), rsc
208 in ic.get_all_connectors().items() ]
212 def render_service_row(self, ctx, data):
213 (service_name, nodeid, rsc) = data
214 ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
215 ctx.fillSlots("nickname", rsc.nickname)
217 rhost = rsc.remote_host
218 if nodeid == IClient(ctx).nodeid:
219 rhost_s = "(loopback)"
220 elif isinstance(rhost, address.IPv4Address):
221 rhost_s = "%s:%d" % (rhost.host, rhost.port)
224 connected = "Yes: to " + rhost_s
225 since = rsc.last_connect_time
228 since = rsc.last_loss_time
230 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
231 ctx.fillSlots("connected", connected)
232 ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
233 ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
234 time.localtime(rsc.announcement_time)))
235 ctx.fillSlots("version", rsc.version)
236 ctx.fillSlots("service_name", rsc.service_name)
240 def render_download_form(self, ctx, data):
241 # this is a form where users can download files by URI
242 form = T.form(action="uri", method="get",
243 enctype="multipart/form-data")[
245 T.legend(class_="freeform-form-label")["Download a file"],
247 T.input(type="text", name="uri"), " ",
248 "Filename to download as: ",
249 T.input(type="text", name="filename"), " ",
250 T.input(type="submit", value="Download!"),
254 def render_view_form(self, ctx, data):
255 # this is a form where users can download files by URI, or jump to a
257 form = T.form(action="uri", method="get",
258 enctype="multipart/form-data")[
260 T.legend(class_="freeform-form-label")["View a file or directory"],
262 T.input(type="text", name="uri"), " ",
263 T.input(type="submit", value="View!"),
267 def render_upload_form(self, ctx, data):
268 # this is a form where users can upload unlinked files
269 form = T.form(action="uri", method="post",
270 enctype="multipart/form-data")[
272 T.legend(class_="freeform-form-label")["Upload a file"],
274 T.input(type="file", name="file", class_="freeform-input-file"),
275 T.input(type="hidden", name="t", value="upload"),
276 " Mutable?:", T.input(type="checkbox", name="mutable"),
277 T.input(type="submit", value="Upload!"),
281 def render_mkdir_form(self, ctx, data):
282 # this is a form where users can create new directories
283 form = T.form(action="uri", method="post",
284 enctype="multipart/form-data")[
286 T.legend(class_="freeform-form-label")["Create a directory"],
287 T.input(type="hidden", name="t", value="mkdir"),
288 T.input(type="hidden", name="redirect_to_result", value="true"),
289 T.input(type="submit", value="Create Directory!"),
293 def render_incident_button(self, ctx, data):
294 # this button triggers a foolscap-logging "incident"
295 form = T.form(action="report_incident", method="post",
296 enctype="multipart/form-data")[
298 T.legend(class_="freeform-form-label")["Report an Incident"],
299 T.input(type="hidden", name="t", value="report-incident"),
300 "What went wrong?: ",
301 T.input(type="text", name="details"), " ",
302 T.input(type="submit", value="Report!"),