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 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!"
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()
125 child_operations = operations.OphandleTable()
127 child_webform_css = webform.defaultCSS
128 child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
130 child_provisioning = provisioning.ProvisioningTool()
131 child_status = status.Status()
132 child_helper_status = status.HelperStatus()
133 child_statistics = status.Statistics()
135 child_report_incident = IncidentReporter()
137 def data_version(self, ctx, data):
138 return get_package_versions_string()
139 def data_import_path(self, ctx, data):
140 return str(allmydata)
141 def data_my_nodeid(self, ctx, data):
142 return idlib.nodeid_b2a(IClient(ctx).nodeid)
143 def data_my_nickname(self, ctx, data):
144 return IClient(ctx).nickname
146 def render_services(self, ctx, data):
148 client = IClient(ctx)
150 ss = client.getServiceNamed("storage")
151 allocated_s = abbreviate_size(ss.allocated_size())
152 allocated = "about %s allocated" % allocated_s
153 sizelimit = "no size limit"
154 if ss.sizelimit is not None:
155 sizelimit = "size limit is %s" % abbreviate_size(ss.sizelimit)
156 ul[T.li["Storage Server: %s, %s" % (allocated, sizelimit)]]
158 ul[T.li["Not running storage server"]]
161 h = client.getServiceNamed("helper")
162 stats = h.get_stats()
163 active_uploads = stats["chk_upload_helper.active_uploads"]
164 ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
166 ul[T.li["Not running helper"]]
170 def data_introducer_furl(self, ctx, data):
171 return IClient(ctx).introducer_furl
172 def data_connected_to_introducer(self, ctx, data):
173 if IClient(ctx).connected_to_introducer():
177 def data_helper_furl(self, ctx, data):
179 uploader = IClient(ctx).getServiceNamed("uploader")
182 furl, connected = uploader.get_helper_info()
184 def data_connected_to_helper(self, ctx, data):
186 uploader = IClient(ctx).getServiceNamed("uploader")
188 return "no" # we don't even have an Uploader
189 furl, connected = uploader.get_helper_info()
194 def data_known_storage_servers(self, ctx, data):
195 ic = IClient(ctx).introducer_client
197 for c in ic.get_all_connectors().values()
198 if c.service_name == "storage"]
201 def data_connected_storage_servers(self, ctx, data):
202 ic = IClient(ctx).introducer_client
203 return len(ic.get_all_connections_for("storage"))
205 def data_services(self, ctx, data):
206 ic = IClient(ctx).introducer_client
207 c = [ (service_name, nodeid, rsc)
208 for (nodeid, service_name), rsc
209 in ic.get_all_connectors().items() ]
213 def render_service_row(self, ctx, data):
214 (service_name, nodeid, rsc) = data
215 ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
216 ctx.fillSlots("nickname", rsc.nickname)
218 rhost = rsc.remote_host
219 if nodeid == IClient(ctx).nodeid:
220 rhost_s = "(loopback)"
221 elif isinstance(rhost, address.IPv4Address):
222 rhost_s = "%s:%d" % (rhost.host, rhost.port)
225 connected = "Yes: to " + rhost_s
226 since = rsc.last_connect_time
229 since = rsc.last_loss_time
231 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
232 ctx.fillSlots("connected", connected)
233 ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
234 ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
235 time.localtime(rsc.announcement_time)))
236 ctx.fillSlots("version", rsc.version)
237 ctx.fillSlots("service_name", rsc.service_name)
241 def render_download_form(self, ctx, data):
242 # this is a form where users can download files by URI
243 form = T.form(action="uri", method="get",
244 enctype="multipart/form-data")[
246 T.legend(class_="freeform-form-label")["Download a file"],
248 T.input(type="text", name="uri"), " ",
249 "Filename to download as: ",
250 T.input(type="text", name="filename"), " ",
251 T.input(type="submit", value="Download!"),
255 def render_view_form(self, ctx, data):
256 # this is a form where users can download files by URI, or jump to a
258 form = T.form(action="uri", method="get",
259 enctype="multipart/form-data")[
261 T.legend(class_="freeform-form-label")["View a file or directory"],
263 T.input(type="text", name="uri"), " ",
264 T.input(type="submit", value="View!"),
268 def render_upload_form(self, ctx, data):
269 # this is a form where users can upload unlinked files
270 form = T.form(action="uri", method="post",
271 enctype="multipart/form-data")[
273 T.legend(class_="freeform-form-label")["Upload a file"],
275 T.input(type="file", name="file", class_="freeform-input-file"),
276 T.input(type="hidden", name="t", value="upload"),
277 " Mutable?:", T.input(type="checkbox", name="mutable"),
278 T.input(type="submit", value="Upload!"),
282 def render_mkdir_form(self, ctx, data):
283 # this is a form where users can create new directories
284 form = T.form(action="uri", method="post",
285 enctype="multipart/form-data")[
287 T.legend(class_="freeform-form-label")["Create a directory"],
288 T.input(type="hidden", name="t", value="mkdir"),
289 T.input(type="hidden", name="redirect_to_result", value="true"),
290 T.input(type="submit", value="Create Directory!"),
294 def render_incident_button(self, ctx, data):
295 # this button triggers a foolscap-logging "incident"
296 form = T.form(action="report_incident", method="post",
297 enctype="multipart/form-data")[
299 T.legend(class_="freeform-form-label")["Report an Incident"],
300 T.input(type="hidden", name="t", value="report-incident"),
301 "What went wrong?: ",
302 T.input(type="text", name="details"), " ",
303 T.input(type="submit", value="Report!"),