netboot: Allow booting multiple architectures
[people/shoehn/ipfire.org.git] / webapp / ui_modules.py
CommitLineData
81675874 1#!/usr/bin/python
2
91a446f0
MT
3from __future__ import division
4
de683d7c 5import hashlib
940227cb 6import logging
91a446f0 7import operator
9068dba1 8import re
940227cb
MT
9import socket
10import textile
11import tornado.escape
6af40477 12import tornado.locale
feb02477 13import tornado.web
9068dba1 14import unicodedata
81675874 15
494d80e6
MT
16import backend
17
81675874 18class UIModule(tornado.web.UIModule):
940227cb
MT
19 @property
20 def accounts(self):
21 return self.handler.accounts
81675874 22
a0048e66
MT
23 @property
24 def advertisements(self):
25 return self.handler.advertisements
26
d0d074e0 27 @property
940227cb
MT
28 def banners(self):
29 return self.handler.banners
d0d074e0 30
de683d7c
MT
31 @property
32 def memcached(self):
33 return self.handler.memcached
34
940227cb
MT
35 @property
36 def releases(self):
37 return self.handler.releases
81675874 38
638e9782
MT
39 @property
40 def geoip(self):
41 return self.handler.geoip
42
7771acea
MT
43 @property
44 def news(self):
45 return self.handler.news
46
fa7e1a0a
MT
47 @property
48 def planet(self):
49 return self.handler.planet
50
77431b9c
MT
51 @property
52 def talk(self):
53 return self.handler.talk
54
e64ce07e
MT
55 @property
56 def wishlist(self):
57 return self.handler.wishlist
58
5a1018ab 59
a0048e66
MT
60class AdvertisementModule(UIModule):
61 def render(self, where):
62 assert where in ("download-splash",), where
63
64 ad = self.advertisements.get(where)
65 if not ad:
66 return ""
67
68 # Mark that advert has been shown.
69 ad.update_impressions()
70
71 return self.render_string("modules/ads/%s.html" % where, ad=ad)
72
73
66862195
MT
74class FireinfoDeviceTableModule(UIModule):
75 def render(self, devices):
76 return self.render_string("fireinfo/modules/table-devices.html",
77 devices=devices)
78
79
80class FireinfoDeviceAndGroupsTableModule(UIModule):
81 def render(self, devices):
82 _ = self.locale.translate
83
84 groups = {}
85
86 for device in devices:
87 if not groups.has_key(device.cls):
88 groups[device.cls] = []
89
90 groups[device.cls].append(device)
91
92 # Sort all devices
93 for key in groups.keys():
94 groups[key].sort()
95
96 # Order the groups by their name
97 groups = groups.items()
98 groups.sort()
99
100 return self.render_string("fireinfo/modules/table-devices-and-groups.html",
101 groups=groups)
102
103
104class FireinfoGeoTableModule(UIModule):
105 def render(self, items):
106 countries = []
107 other_countries = []
108 for code, value in items:
109 # Skip the satellite providers in this ranking
110 if code in (None, "A1", "A2"):
111 continue
112
113 name = self.geoip.get_country_name(code)
114
115 # Don't add countries with a small share on the list
116 if value < 0.01:
117 other_countries.append(name)
118 continue
119
494d80e6 120 country = backend.database.Row({
66862195
MT
121 "code" : code,
122 "name" : name,
123 "value" : value,
124 })
125 countries.append(country)
126
66862195
MT
127 return self.render_string("fireinfo/modules/table-geo.html",
128 countries=countries, other_countries=other_countries)
129
130
131class LanguageNameModule(UIModule):
132 def render(self, language):
133 _ = self.locale.translate
134
135 if language == "de":
136 return _("German")
137 elif language == "en":
138 return _("English")
139 elif language == "es":
140 return _("Spanish")
141 elif language == "fr":
142 return _("French")
143 elif language == "it":
144 return _("Italian")
145 elif language == "nl":
146 return _("Dutch")
147 elif language == "pl":
148 return _("Polish")
149 elif language == "pt":
150 return _("Portuguese")
151 elif language == "ru":
152 return _("Russian")
153 elif language == "tr":
154 return _("Turkish")
155
156 return language
157
158
9068dba1
MT
159class MapModule(UIModule):
160 def render(self, latitude, longitude):
161 return self.render_string("modules/map.html", latitude=latitude, longitude=longitude)
162
163
940227cb
MT
164class MenuModule(UIModule):
165 def render(self):
60024cc8 166 return self.render_string("modules/menu.html")
81675874 167
168
9068dba1
MT
169class MirrorItemModule(UIModule):
170 def render(self, item):
171 return self.render_string("modules/mirror-item.html", item=item)
172
173
174class MirrorsTableModule(UIModule):
175 def render(self, mirrors, preferred_mirrors=[]):
176 return self.render_string("modules/mirrors-table.html",
177 mirrors=mirrors, preferred_mirrors=preferred_mirrors)
178
179
180class NetBootMenuConfigModule(UIModule):
37b5c0cf
MT
181 def render(self, release, arch=None, platform=None):
182 return self.render_string("netboot/menu-config.cfg", release=release,
183 arch=arch, platform=platform)
9068dba1
MT
184
185
186class NetBootMenuHeaderModule(UIModule):
37b5c0cf 187 def render(self, title, releases, arch=None, platform=None):
9068dba1
MT
188 id = unicodedata.normalize("NFKD", unicode(title)).encode("ascii", "ignore")
189 id = re.sub(r"[^\w]+", " ", id)
190 id = "-".join(id.lower().strip().split())
191
192 return self.render_string("netboot/menu-header.cfg", id=id,
37b5c0cf 193 title=title, releases=releases, arch=arch, platform=platform)
9068dba1
MT
194
195
196class NetBootMenuSeparatorModule(UIModule):
197 def render(self):
198 return self.render_string("netboot/menu-separator.cfg")
199
200
81675874 201class NewsItemModule(UIModule):
940227cb
MT
202 def get_author(self, author):
203 # Get name of author
204 author = self.accounts.find(author)
205 if author:
2fed2438 206 return author.name
940227cb
MT
207 else:
208 _ = self.locale.translate
209 return _("Unknown author")
210
60024cc8 211 def render(self, item, uncut=True, announcement=False, show_heading=True):
940227cb
MT
212 # Get author
213 item.author = self.get_author(item.author_id)
81675874 214
940227cb
MT
215 if not uncut and len(item.text) >= 400:
216 item.text = item.text[:400] + "..."
81675874 217
940227cb 218 # Render text
e46e8df8 219 item.text = textile.textile(item.text.decode("utf8"))
81675874 220
9de13943
MT
221 # Find a release if one exists
222 release = self.releases.get_by_news_id(item.uuid)
223
224 return self.render_string("modules/news-item.html", item=item, release=release,
60024cc8 225 uncut=uncut, announcement=announcement, show_heading=show_heading)
940227cb
MT
226
227
7771acea 228class NewsLineModule(UIModule):
940227cb
MT
229 def render(self, item):
230 return self.render_string("modules/news-line.html", item=item)
231
232
7771acea
MT
233class NewsTableModule(UIModule):
234 def render(self, news):
235 return self.render_string("modules/news-table.html", news=news)
236
237
238class NewsYearNavigationModule(UIModule):
239 def render(self, active=None):
240 try:
241 active = int(active)
242 except:
243 active = None
244
245 return self.render_string("modules/news-year-nav.html",
246 active=active, years=self.news.years)
247
248
fa7e1a0a
MT
249class PlanetSearchBoxModule(UIModule):
250 def render(self, query=None):
60b0917c 251 return self.render_string("modules/planet/search-box.html", query=query)
fa7e1a0a
MT
252
253
81675874 254class SidebarItemModule(UIModule):
255 def render(self):
256 return self.render_string("modules/sidebar-item.html")
257
258
259class SidebarReleaseModule(UIModule):
81675874 260 def render(self):
261 return self.render_string("modules/sidebar-release.html",
940227cb 262 latest=self.releases.get_latest())
81675874 263
264
265class ReleaseItemModule(UIModule):
60024cc8 266 def render(self, release, latest=False):
110e8687 267 arches = ("i586", "arm")
60024cc8 268
110e8687
MT
269 downloads = []
270 for arch in ("i586", "arm"):
271 files = []
272
273 for file in release.files:
274 if not file.arch == arch:
275 continue
276
277 files.append(file)
278
279 if files:
280 downloads.append((arch, files))
60024cc8
MT
281
282 return self.render_string("modules/release-item.html",
110e8687 283 release=release, latest=latest, downloads=downloads)
81675874 284
285
286class SidebarBannerModule(UIModule):
940227cb
MT
287 def render(self, item=None):
288 if not item:
289 item = self.banners.get_random()
290
81675874 291 return self.render_string("modules/sidebar-banner.html", item=item)
292
293
60024cc8
MT
294class DownloadButtonModule(UIModule):
295 def render(self, release, text="Download now!"):
296 best_image = None
297
298 for file in release.files:
299 if file.type == "iso":
300 best_image = file
301 break
302
303 # Show nothing when there was no image found.
304 if not best_image:
305 return ""
306
307 return self.render_string("modules/download-button.html",
308 release=release, image=best_image)
309
310
d88b8f41
MT
311class PlanetAuthorBoxModule(UIModule):
312 def render(self, author):
313 return self.render_string("planet/modules/author-box.html", author=author)
314
315
940227cb 316class PlanetEntryModule(UIModule):
60024cc8 317 def render(self, entry, show_avatar=True):
940227cb 318 return self.render_string("modules/planet-entry.html",
60024cc8 319 entry=entry, show_avatar=show_avatar)
d0d074e0
MT
320
321
66862195
MT
322class ProgressBarModule(UIModule):
323 def render(self, value, colour=None):
324 value *= 100
325
326 return self.render_string("modules/progress-bar.html",
327 colour=colour, value=value)
328
329
330class TalkCallLogModule(UIModule):
77431b9c
MT
331 def render(self, account=None, viewer=None):
332 if (account is None or not self.current_user == account) \
333 and not self.current_user.is_admin():
334 raise RuntimeException("Insufficient permissions")
335
336 if viewer is None:
337 viewer = self.current_user
338
339 calls = self.talk.get_call_log(account)
340
341 return self.render_string("talk/modules/call-log.html",
342 calls=calls, viewer=viewer)
343
344
345class TalkLinesModule(UIModule):
346 def render(self, account=None, show_account=False):
347 if (account is None or not self.current_user == account) \
348 and not self.current_user.is_admin():
349 raise RuntimeException("Insufficient permissions")
350
351 lines = self.talk.get_lines(account)
352
353 return self.render_string("talk/modules/lines.html",
354 show_account=show_account, lines=lines)
66862195
MT
355
356
357class TalkOngoingCallsModule(UIModule):
77431b9c
MT
358 def render(self, account=None):
359 if (account is None or not self.current_user == account) \
360 and not self.current_user.is_admin():
361 raise RuntimeException("Insufficient permissions")
362
363 calls = self.talk.get_ongoing_calls(account)
364
365 if calls:
366 return self.render_string("talk/modules/ongoing-calls.html",
367 calls=calls)
368
369 return ""
66862195
MT
370
371
940227cb 372class TrackerPeerListModule(UIModule):
6d524cba 373 def render(self, peers):
940227cb
MT
374 # Guess country code and hostname of the host
375 for peer in peers:
6d524cba
MT
376 country_code = self.geoip.get_country(peer["ip"])
377 if country_code:
66862195
MT
378 peer["country_code"] = country_code.lower()
379 else:
380 peer["country_code"] = "unknown"
d0d074e0 381
940227cb
MT
382 try:
383 peer["hostname"] = socket.gethostbyaddr(peer["ip"])[0]
384 except:
385 peer["hostname"] = ""
d0d074e0 386
940227cb 387 return self.render_string("modules/tracker-peerlist.html",
e46e8df8 388 peers=[backend.database.Row(p) for p in peers])
372efc19
MT
389
390
7771acea
MT
391class WishlistModule(UIModule):
392 def render(self, wishes, short=False):
393 return self.render_string("wishlist/modules/wishlist.html",
394 wishes=wishes, short=short)
395
396
397class WishModule(UIModule):
398 def render(self, wish, short=False):
3907b320 399 progress_bar = "progress-bar-warning"
7771acea 400
f60b79ce 401 if wish.percentage >= 100:
3907b320 402 progress_bar = "progress-bar-success"
7771acea
MT
403
404 return self.render_string("wishlist/modules/wish.html",
405 wish=wish, short=short, progress_bar=progress_bar)
406
407
e64ce07e
MT
408class WishlistItemsModule(UIModule):
409 def render(self, wishlist_items):
410 return self.render_string("modules/wishlist-items.html",
411 wishlist_items=wishlist_items)
412
413
7771acea 414class DonationBoxModule(UIModule):
e00c06b9 415 def render(self, reason_for_transfer=None):
353880e5
MT
416 if reason_for_transfer:
417 reason_for_transfer = "IPFire.org - %s" % reason_for_transfer
418
419 return self.render_string("modules/donation-box.html",
e00c06b9
MT
420 reason_for_transfer=reason_for_transfer)
421
422
423class DonationButtonModule(UIModule):
069e18f5
MT
424 # https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/Appx_websitestandard_htmlvariables/
425 COUNTRIES = (
426 "AU",
427 "AT",
428 "BE",
429 "BR",
430 "CA",
431 "CH",
432 "CN",
433 "DE",
434 "ES",
435 "GB",
436 "FR",
437 "IT",
438 "NL",
439 "PL",
440 "PT",
441 "RU",
442 "US",
443 )
444
445 LOCALES = (
446 "da_DK",
447 "he_IL",
448 "id_ID",
449 "ja_JP",
450 "no_NO",
451 "pt_BR",
452 "ru_RU",
453 "sv_SE",
454 "th_TH",
455 "zh_CN",
456 "zh_HK",
457 "zh_TW",
458 )
459
e00c06b9
MT
460 def render(self, reason_for_transfer=None, currency="EUR"):
461 if not reason_for_transfer:
462 reason_for_transfer = "IPFire.org"
463
464 primary = (currency == "EUR")
465
466 return self.render_string("modules/donation-button.html", primary=primary,
069e18f5
MT
467 reason_for_transfer=reason_for_transfer, currency=currency, lc=self.lc)
468
469 @property
470 def lc(self):
471 """
472 Returns the locale of the user
473 """
474 try:
475 locale, delimiter, encoding = self.locale.code.partition(".")
476
477 # Break for languages in specific countries
478 if locale in self.LOCALES:
479 return locale
480
481 lang, delimiter, country_code = locale.partition("_")
069e18f5
MT
482
483 if country_code and country_code in self.COUNTRIES:
484 return country_code
485
486 lang = lang.upper()
487 if lang in self.COUNTRIES:
488 return lang
489 except:
490 pass
491
492 # If anything goes wrong, fall back to GB
493 return "GB"
60b0917c
MT
494
495
496class DonationInputBoxModule(DonationButtonModule):
497 def render(self):
498 currencies = ("EUR", "USD", "GBP", "CHF", "AUD", "NZD", "CAD")
499
500 return self.render_string("modules/donation-input-box.html",
501 currencies=currencies, lc=self.lc)