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 def __init__(self, original=None):
122 rend.Page.__init__(self, original)
123 self.child_operations = operations.OphandleTable()
125 child_uri = URIHandler()
126 child_cap = URIHandler()
127 child_file = FileHandler()
128 child_named = FileHandler()
130 child_webform_css = webform.defaultCSS
131 child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
133 child_provisioning = provisioning.ProvisioningTool()
134 child_status = status.Status()
135 child_helper_status = status.HelperStatus()
136 child_statistics = status.Statistics()
138 child_report_incident = IncidentReporter()
140 def data_version(self, ctx, data):
141 return get_package_versions_string()
142 def data_import_path(self, ctx, data):
143 return str(allmydata)
144 def data_my_nodeid(self, ctx, data):
145 return idlib.nodeid_b2a(IClient(ctx).nodeid)
146 def data_my_nickname(self, ctx, data):
147 return IClient(ctx).nickname
149 def render_services(self, ctx, data):
151 client = IClient(ctx)
153 ss = client.getServiceNamed("storage")
154 allocated_s = abbreviate_size(ss.allocated_size())
155 allocated = "about %s allocated" % allocated_s
156 reserved = "%s reserved" % abbreviate_size(ss.reserved_space)
157 ul[T.li["Storage Server: %s, %s" % (allocated, reserved)]]
159 ul[T.li["Not running storage server"]]
162 h = client.getServiceNamed("helper")
163 stats = h.get_stats()
164 active_uploads = stats["chk_upload_helper.active_uploads"]
165 ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
167 ul[T.li["Not running helper"]]
171 def data_introducer_furl(self, ctx, data):
172 return IClient(ctx).introducer_furl
173 def data_connected_to_introducer(self, ctx, data):
174 if IClient(ctx).connected_to_introducer():
178 def data_helper_furl(self, ctx, data):
180 uploader = IClient(ctx).getServiceNamed("uploader")
183 furl, connected = uploader.get_helper_info()
185 def data_connected_to_helper(self, ctx, data):
187 uploader = IClient(ctx).getServiceNamed("uploader")
189 return "no" # we don't even have an Uploader
190 furl, connected = uploader.get_helper_info()
195 def data_known_storage_servers(self, ctx, data):
196 ic = IClient(ctx).introducer_client
198 for c in ic.get_all_connectors().values()
199 if c.service_name == "storage"]
202 def data_connected_storage_servers(self, ctx, data):
203 ic = IClient(ctx).introducer_client
204 return len(ic.get_all_connections_for("storage"))
206 def data_services(self, ctx, data):
207 ic = IClient(ctx).introducer_client
208 c = [ (service_name, nodeid, rsc)
209 for (nodeid, service_name), rsc
210 in ic.get_all_connectors().items() ]
214 def render_service_row(self, ctx, data):
215 (service_name, nodeid, rsc) = data
216 ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
217 ctx.fillSlots("nickname", rsc.nickname)
219 rhost = rsc.remote_host
220 if nodeid == IClient(ctx).nodeid:
221 rhost_s = "(loopback)"
222 elif isinstance(rhost, address.IPv4Address):
223 rhost_s = "%s:%d" % (rhost.host, rhost.port)
226 connected = "Yes: to " + rhost_s
227 since = rsc.last_connect_time
230 since = rsc.last_loss_time
232 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
233 ctx.fillSlots("connected", connected)
234 ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
235 ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
236 time.localtime(rsc.announcement_time)))
237 ctx.fillSlots("version", rsc.version)
238 ctx.fillSlots("service_name", rsc.service_name)
242 def render_download_form(self, ctx, data):
243 # this is a form where users can download files by URI
244 form = T.form(action="uri", method="get",
245 enctype="multipart/form-data")[
247 T.legend(class_="freeform-form-label")["Download a file"],
249 T.input(type="text", name="uri"), " ",
250 "Filename to download as: ",
251 T.input(type="text", name="filename"), " ",
252 T.input(type="submit", value="Download!"),
256 def render_view_form(self, ctx, data):
257 # this is a form where users can download files by URI, or jump to a
259 form = T.form(action="uri", method="get",
260 enctype="multipart/form-data")[
262 T.legend(class_="freeform-form-label")["View a file or directory"],
264 T.input(type="text", name="uri"), " ",
265 T.input(type="submit", value="View!"),
269 def render_upload_form(self, ctx, data):
270 # this is a form where users can upload unlinked files
271 form = T.form(action="uri", method="post",
272 enctype="multipart/form-data")[
274 T.legend(class_="freeform-form-label")["Upload a file"],
276 T.input(type="file", name="file", class_="freeform-input-file"),
277 T.input(type="hidden", name="t", value="upload"),
278 " Mutable?:", T.input(type="checkbox", name="mutable"),
279 T.input(type="submit", value="Upload!"),
283 def render_mkdir_form(self, ctx, data):
284 # this is a form where users can create new directories
285 form = T.form(action="uri", method="post",
286 enctype="multipart/form-data")[
288 T.legend(class_="freeform-form-label")["Create a directory"],
289 T.input(type="hidden", name="t", value="mkdir"),
290 T.input(type="hidden", name="redirect_to_result", value="true"),
291 T.input(type="submit", value="Create Directory!"),
295 def render_incident_button(self, ctx, data):
296 # this button triggers a foolscap-logging "incident"
297 form = T.form(action="report_incident", method="post",
298 enctype="multipart/form-data")[
300 T.legend(class_="freeform-form-label")["Report an Incident"],
301 T.input(type="hidden", name="t", value="report-incident"),
302 "What went wrong?: ",
303 T.input(type="text", name="details"), " ",
304 T.input(type="submit", value="Report!"),