]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/web/root.py
storage: replace sizelimit with reserved_space, make the stats 'disk_avail' number...
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / web / root.py
1
2 import time
3
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
11
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
20
21
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.
25
26     def render_GET(self, ctx):
27         req = IRequest(ctx)
28         uri = get_arg(req, "uri", None)
29         if uri is 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)
36         return there
37
38     def render_PUT(self, ctx):
39         req = IRequest(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()
43         if t == "":
44             mutable = bool(get_arg(req, "mutable", "").strip())
45             if mutable:
46                 return unlinked.PUTUnlinkedSSK(ctx)
47             else:
48                 return unlinked.PUTUnlinkedCHK(ctx)
49         if t == "mkdir":
50             return unlinked.PUTUnlinkedCreateDirectory(ctx)
51         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
52                   "and POST?t=mkdir")
53         raise WebError(errmsg, http.BAD_REQUEST)
54
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
58         # new directory
59         req = IRequest(ctx)
60         t = get_arg(req, "t", "").strip()
61         if t in ("", "upload"):
62             mutable = bool(get_arg(req, "mutable", "").strip())
63             if mutable:
64                 return unlinked.POSTUnlinkedSSK(ctx)
65             else:
66                 return unlinked.POSTUnlinkedCHK(ctx)
67         if t == "mkdir":
68             return unlinked.POSTUnlinkedCreateDirectory(ctx)
69         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
70                   "and POST?t=mkdir")
71         raise WebError(errmsg, http.BAD_REQUEST)
72
73     def childFactory(self, ctx, name):
74         # 'name' is expected to be a URI
75         client = IClient(ctx)
76         try:
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"
81                            % name)
82
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".
86
87     def childFactory(self, ctx, name):
88         req = IRequest(ctx)
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
92         client = IClient(ctx)
93         try:
94             node = client.create_node_from_uri(name)
95         except (TypeError, AssertionError):
96             raise WebError("'%s' is not a valid file- or directory- cap"
97                            % name)
98         if not IFileNode.providedBy(node):
99             raise WebError("'%s' is not a file-cap" % name)
100         return filenode.FileNodeDownloadHandler(node)
101
102     def renderHTTP(self, ctx):
103         raise WebError("/file must be followed by a file-cap and a name",
104                        http.NOT_FOUND)
105
106 class IncidentReporter(RenderMixin, rend.Page):
107     def render_POST(self, ctx):
108         req = IRequest(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!"
114
115
116 class Root(rend.Page):
117
118     addSlash = True
119     docFactory = getxmlfile("welcome.xhtml")
120
121     def __init__(self, original=None):
122         rend.Page.__init__(self, original)
123         self.child_operations = operations.OphandleTable()
124
125     child_uri = URIHandler()
126     child_cap = URIHandler()
127     child_file = FileHandler()
128     child_named = FileHandler()
129
130     child_webform_css = webform.defaultCSS
131     child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
132
133     child_provisioning = provisioning.ProvisioningTool()
134     child_status = status.Status()
135     child_helper_status = status.HelperStatus()
136     child_statistics = status.Statistics()
137
138     child_report_incident = IncidentReporter()
139
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
148
149     def render_services(self, ctx, data):
150         ul = T.ul()
151         client = IClient(ctx)
152         try:
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)]]
158         except KeyError:
159             ul[T.li["Not running storage server"]]
160
161         try:
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,)]]
166         except KeyError:
167             ul[T.li["Not running helper"]]
168
169         return ctx.tag[ul]
170
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():
175             return "yes"
176         return "no"
177
178     def data_helper_furl(self, ctx, data):
179         try:
180             uploader = IClient(ctx).getServiceNamed("uploader")
181         except KeyError:
182             return None
183         furl, connected = uploader.get_helper_info()
184         return furl
185     def data_connected_to_helper(self, ctx, data):
186         try:
187             uploader = IClient(ctx).getServiceNamed("uploader")
188         except KeyError:
189             return "no" # we don't even have an Uploader
190         furl, connected = uploader.get_helper_info()
191         if connected:
192             return "yes"
193         return "no"
194
195     def data_known_storage_servers(self, ctx, data):
196         ic = IClient(ctx).introducer_client
197         servers = [c
198                    for c in ic.get_all_connectors().values()
199                    if c.service_name == "storage"]
200         return len(servers)
201
202     def data_connected_storage_servers(self, ctx, data):
203         ic = IClient(ctx).introducer_client
204         return len(ic.get_all_connections_for("storage"))
205
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() ]
211         c.sort()
212         return c
213
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)
218         if rsc.rref:
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)
224             else:
225                 rhost_s = str(rhost)
226             connected = "Yes: to " + rhost_s
227             since = rsc.last_connect_time
228         else:
229             connected = "No"
230             since = rsc.last_loss_time
231
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)
239
240         return ctx.tag
241
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")[
246             T.fieldset[
247             T.legend(class_="freeform-form-label")["Download a file"],
248             "URI to download: ",
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!"),
253             ]]
254         return T.div[form]
255
256     def render_view_form(self, ctx, data):
257         # this is a form where users can download files by URI, or jump to a
258         # named directory
259         form = T.form(action="uri", method="get",
260                       enctype="multipart/form-data")[
261             T.fieldset[
262             T.legend(class_="freeform-form-label")["View a file or directory"],
263             "URI to view: ",
264             T.input(type="text", name="uri"), " ",
265             T.input(type="submit", value="View!"),
266             ]]
267         return T.div[form]
268
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")[
273             T.fieldset[
274             T.legend(class_="freeform-form-label")["Upload a file"],
275             "Choose 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!"),
280             ]]
281         return T.div[form]
282
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")[
287             T.fieldset[
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!"),
292             ]]
293         return T.div[form]
294
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")[
299             T.fieldset[
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!"),
305             ]]
306         return T.div[form]