]> git.ipfire.org Git - people/jschlag/pbs.git/blob - src/web/ui_modules.py
Merge branch 'master' of git://git.ipfire.org/pbs
[people/jschlag/pbs.git] / src / web / ui_modules.py
1 #!/usr/bin/python
2
3 from __future__ import division
4
5 import datetime
6 import itertools
7 import math
8 import pytz
9 import re
10 import string
11 import tornado.escape
12 import tornado.web
13
14 from .. import users
15 from ..constants import *
16
17 class UIModule(tornado.web.UIModule):
18 @property
19 def backend(self):
20 return self.handler.application.backend
21
22
23 class TextModule(UIModule):
24 BUGZILLA_PATTERN = re.compile(r"(?:bug\s?|#)(\d+)")
25 CVE_PATTERN = re.compile(r"(?:CVE)[\s\-](\d{4}\-\d{4})")
26
27 LINK = """<a href="%s" target="_blank" rel="noopener">%s</a>"""
28
29 def split_paragraphs(self, s):
30 for group_seperator, line_iteration in itertools.groupby(s.splitlines(True), key=str.isspace):
31 if group_seperator:
32 continue
33
34 paragraph = "".join(line_iteration)
35 yield paragraph.replace("\n", " ")
36
37 def render(self, text, pre=False, remove_linebreaks=True):
38 if remove_linebreaks:
39 text = text.replace("\n", " ")
40
41 # Escape the text and create make urls clickable.
42 text = tornado.escape.xhtml_escape(text)
43 text = tornado.escape.linkify(text, shorten=True,
44 extra_params="target=\"_blank\" rel=\"noopener\"")
45
46 # Search for bug ids that need to be linked to bugzilla
47 text = re.sub(self.BUGZILLA_PATTERN, self._bugzilla_repl, text, re.I|re.U)
48
49 # Search for CVE numbers and create hyperlinks.
50 text = re.sub(self.CVE_PATTERN, self._cve_repl, text, re.I|re.U)
51
52 if pre:
53 return "<pre>%s</pre>" % text
54
55 return text
56
57 def _bugzilla_repl(self, m):
58 bug_id = m.group(1)
59
60 # Get the URL
61 bug_url = self.backend.bugzilla.bug_url(bug_id)
62
63 return self.LINK % (bug_url, m.group(0))
64
65 def _cve_repl(self, m):
66 return self.LINK % ("http://cve.mitre.org/cgi-bin/cvename.cgi?name=%s" % m.group(1), m.group(0))
67
68
69 class CommitMessageModule(TextModule):
70 def render(self, commit):
71 s = "h5. %s\n\n" % commit.subject
72
73 paragraphs = self.split_paragraphs(commit.message)
74 s += "\n\n".join(paragraphs)
75
76 return TextModule.render(self, s, remove_linebreaks=False)
77
78
79 class ModalModule(UIModule):
80 def render(self, what, **kwargs):
81 what = "modules/modal-%s.html" % what
82
83 return self.render_string(what, **kwargs)
84
85
86 class BuildHeadlineModule(UIModule):
87 def render(self, build, short=False, shorter=False):
88 if shorter:
89 short = True
90
91 return self.render_string("modules/build-headline.html",
92 build=build, pkg=build.pkg, short=short, shorter=shorter)
93
94
95 class JobsStatusModule(UIModule):
96 def render(self, build):
97 return self.render_string("modules/jobs/status.html",
98 build=build, jobs=build.jobs)
99
100
101 class BugsTableModule(UIModule):
102 def render(self, pkg, bugs):
103 return self.render_string("modules/bugs-table.html",
104 pkg=pkg, bugs=bugs)
105
106
107 class ChangelogModule(UIModule):
108 def render(self, name=None, builds=None, *args, **kwargs):
109 if not builds:
110 builds = self.backend.builds.get_changelog(name, *args, **kwargs)
111
112 return self.render_string("modules/changelog/index.html", builds=builds)
113
114
115 class ChangelogEntryModule(UIModule):
116 def render(self, build):
117 return self.render_string("modules/changelog/entry.html", build=build)
118
119
120 class CommitsTableModule(UIModule):
121 def render(self, distro, source, commits, full_format=True):
122 return self.render_string("modules/commits-table.html",
123 distro=distro, source=source, commits=commits,
124 full_format=full_format)
125
126
127 class FooterModule(UIModule):
128 def render(self):
129 return self.render_string("modules/footer.html")
130
131
132 class HeadingDateModule(UIModule):
133 def render(self, date):
134 _ = self.locale.translate
135
136 # Check if this is today.
137 today = datetime.date.today()
138 if date == today:
139 return _("Today")
140
141 # Check if this was yesterday.
142 yesterday = today - datetime.timedelta(days=1)
143 if date == yesterday:
144 return _("Yesterday")
145
146 # Convert date to datetime.
147 date = datetime.datetime(date.year, date.month, date.day)
148
149 return self.locale.format_date(date, shorter=True, relative=False)
150
151
152 class PackagesTableModule(UIModule):
153 def render(self, job, packages):
154 return self.render_string("modules/packages-table.html", job=job,
155 packages=packages)
156
157
158 class PackagesDependencyTableModule(UIModule):
159 def render(self, pkg):
160 if pkg.type == "source":
161 all_deps = [
162 (None, pkg.requires),
163 ]
164 else:
165 all_deps = [
166 ("provides", pkg.provides),
167 ("requires", pkg.requires),
168 ("prerequires", pkg.prerequires),
169 ("conflicts", pkg.conflicts),
170 ("obsoletes", pkg.obsoletes),
171 ("recommends", pkg.recommends),
172 ("suggests", pkg.suggests),
173 ]
174
175 has_deps = []
176 for name, deps in all_deps:
177 if deps:
178 has_deps.append((name, deps))
179
180 if len(has_deps):
181 span = math.floor(12 / len(has_deps))
182
183 if span > 3:
184 span = 3
185 else:
186 span = 12
187
188 return self.render_string("modules/packages/dependency-table.html",
189 pkg=pkg, dependencies=has_deps, span=span)
190
191
192 class PackageTable2Module(UIModule):
193 def render(self, packages):
194 return self.render_string("modules/package-table-detail.html",
195 packages=packages)
196
197
198 class FilesTableModule(UIModule):
199 def render(self, files):
200 return self.render_string("modules/files-table.html", files=files)
201
202
203 class LogFilesTableModule(UIModule):
204 def render(self, job, files):
205 return self.render_string("modules/log-files-table.html", job=job,
206 files=files)
207
208
209 class PackageHeaderModule(UIModule):
210 def render(self, pkg):
211 return self.render_string("modules/package-header.html", pkg=pkg)
212
213
214 class PackageFilesTableModule(UIModule):
215 def render(self, pkg, filelist):
216 return self.render_string("modules/packages-files-table.html",
217 pkg=pkg, filelist=filelist)
218
219
220 class BuildTableModule(UIModule):
221 def render(self, builds, **kwargs):
222 settings = dict(
223 show_user = False,
224 show_repo = False,
225 show_repo_time = False,
226 show_can_move_forward = False,
227 show_when = True,
228 )
229 settings.update(kwargs)
230
231 dates = {}
232
233 for b in builds:
234 try:
235 dates[b.date].append(b)
236 except KeyError:
237 dates[b.date] = [b,]
238
239 dates = sorted(dates.items(), reverse=True)
240
241 return self.render_string("modules/build-table.html", dates=dates, **settings)
242
243
244 class BuildStateWarningsModule(UIModule):
245 def render(self, build):
246 return self.render_string("modules/build-state-warnings.html", build=build)
247
248
249 class JobsBoxesModule(UIModule):
250 def render(self, build, jobs=None):
251 if jobs is None:
252 jobs = build.jobs
253
254 return self.render_string("modules/jobs/boxes.html",
255 build=build, jobs=jobs)
256
257
258 class JobStateModule(UIModule):
259 def render(self, job, cls=None, show_arch=False, show_icon=False, plain=False):
260 state = job.state
261
262 _ = self.locale.translate
263 classes = []
264
265 icon = None
266 if state == "aborted":
267 text = _("Aborted")
268 classes.append("muted")
269 icon = "icon-warning-sign"
270
271 elif state == "dependency_error":
272 text = _("Dependency problem")
273 classes.append("text-warning")
274 icon = "icon-random"
275
276 elif state == "dispatching":
277 text = _("Dispatching")
278 classes.append("text-info")
279 icon = "icon-download-alt"
280
281 elif state == "failed":
282 text = _("Failed")
283 classes.append("text-error")
284 icon = "icon-remove"
285
286 elif state == "finished":
287 text = _("Finished")
288 classes.append("text-success")
289 icon = "icon-ok"
290
291 elif state == "new":
292 text = _("New")
293 classes.append("muted")
294 icon = "icon-asterisk"
295
296 elif state == "pending":
297 text = _("Pending")
298 classes.append("muted")
299 icon = "icon-time"
300
301 elif state == "running":
302 text = _("Running")
303 classes.append("text-info")
304 icon = "icon-cogs"
305
306 elif state == "uploading":
307 text = _("Uploading")
308 classes.append("text-info")
309 icon = "icon-upload-alt"
310
311 # Return just the string, is state is unknown.
312 else:
313 text = _("Unknown: %s") % state
314 classes.append("muted")
315
316 if plain:
317 return text
318
319 if cls:
320 classes.append(cls)
321
322 if show_arch:
323 text = job.arch
324
325 if show_icon and icon:
326 text = """<i class="%s"></i> %s""" % (icon, text)
327
328 return """<span class="%s">%s</span>""" % (" ".join(classes), text)
329
330
331 class JobsTableModule(UIModule):
332 def render(self, build, jobs=None, type="release"):
333 if jobs is None:
334 jobs = build.jobs
335
336 return self.render_string("modules/jobs-table.html", build=build,
337 jobs=jobs, type=type)
338
339
340 class JobsListModule(UIModule):
341 def render(self, jobs):
342 return self.render_string("modules/jobs/list.html", jobs=jobs)
343
344
345 class RepositoryTableModule(UIModule):
346 def render(self, distro, repos):
347 return self.render_string("modules/repository-table.html",
348 distro=distro, repos=repos)
349
350
351 class SourceTableModule(UIModule):
352 def render(self, distro, sources):
353 return self.render_string("modules/source-table.html",
354 distro=distro, sources=sources)
355
356
357 class CommentsTableModule(UIModule):
358 def render(self, comments, show_package=False, show_user=True):
359 pkgs, users = {}, {}
360 for comment in comments:
361 if show_package:
362 try:
363 pkg = pkgs[comment.pkg_id]
364 except KeyError:
365 pkg = pkgs[comment.pkg_id] = \
366 self.backend.packages.get_by_id(comment.pkg_id)
367
368 comment["pkg"] = pkg
369
370 if show_user:
371 try:
372 user = users[comment.user_id]
373 except KeyError:
374 user = users[comment.user_id] = \
375 self.backend.users.get_by_id(comment.user_id)
376
377 comment["user"] = user
378
379 return self.render_string("modules/comments-table.html",
380 comments=comments, show_package=show_package, show_user=show_user)
381
382
383 class LogModule(UIModule):
384 def render(self, entries, **args):
385 return self.render_string("modules/log.html",
386 entries=entries, args=args)
387
388
389 class LogEntryModule(UIModule):
390 def render(self, entry, small=None, **args):
391 if small or not entry.user:
392 template = "modules/log-entry-small.html"
393 else:
394 template = "modules/log-entry.html"
395
396 return self.render_string(template, entry=entry, u=entry.user,
397 show_build=False, **args)
398
399
400 class LogEntryCommentModule(LogEntryModule):
401 def render(self, entry, show_build=False, **args):
402 return self.render_string("modules/log-entry-comment.html",
403 entry=entry, u=entry.user, show_build=show_build, **args)
404
405
406 class MaintainerModule(UIModule):
407 def render(self, maintainer):
408 if isinstance(maintainer, users.User):
409 type = "user"
410 else:
411 type = "string"
412
413 return self.render_string("modules/maintainer.html",
414 type=type, maintainer=maintainer)
415
416
417 class BuildLogModule(UIModule):
418 # XXX deprecated
419 def render(self, messages):
420 _ = self.locale.translate
421
422 for message in messages:
423 try:
424 msg = LOG2MSG[message.message]
425 message["message"] = _(msg)
426 except KeyError:
427 pass
428
429 return self.render_string("modules/build-log.html", messages=messages)
430
431
432 class LogTableModule(UIModule):
433 def render(self, messages, links=["pkg",]):
434 for message in messages:
435 try:
436 message["message"] = LOG2MSG[message.message]
437 except KeyError:
438 pass
439
440 if message.build_id:
441 message["build"] = self.backend.builds.get_by_id(message.build_id)
442
443 elif message.pkg_id:
444 message["pkg"] = self.backend.packages.get_by_id(message.pkg_id)
445
446 return self.render_string("modules/log-table.html",
447 messages=messages, links=links)
448
449
450 class UsersTableModule(UIModule):
451 def render(self, users):
452 return self.render_string("modules/user-table.html", users=users)
453
454
455 class BuildOffsetModule(UIModule):
456 def render(self):
457 return self.render_string("modules/build-offset.html")
458
459
460 class RepoActionsTableModule(UIModule):
461 def render(self, repo):
462 actions = repo.get_actions()
463
464 return self.render_string("modules/repo-actions-table.html",
465 repo=repo, actions=actions)
466
467
468 class UpdatesTableModule(UIModule):
469 def render(self, updates):
470 return self.render_string("modules/updates-table.html", updates=updates)
471
472
473 class WatchersSidebarTableModule(UIModule):
474 def css_files(self):
475 return "css/watchers-sidebar-table.css"
476
477 def render(self, build, watchers, limit=5):
478 # Sort the watchers by their realname.
479 watchers.sort(key=lambda watcher: watcher.realname)
480
481 return self.render_string("modules/watchers-sidebar-table.html",
482 build=build, watchers=watchers, limit=limit)
483
484
485 class SelectLocaleModule(UIModule):
486 LOCALE_NAMES = [
487 # local code, English name, name
488 ("ca_ES", u"Catalan", "Catal\xc3\xa0"),
489 ("da_DK", u"Danish", u"Dansk"),
490 ("de_DE", u"German", u"Deutsch"),
491 ("en_GB", u"English (UK)", u"English (UK)"),
492 ("en_US", u"English (US)", u"English (US)"),
493 ("es_ES", u"Spanish (Spain)", u"Espa\xf1ol (Espa\xf1a)"),
494 ("es_LA", u"Spanish", u"Espa\xf1ol"),
495 ("fr_CA", u"French (Canada)", u"Fran\xe7ais (Canada)"),
496 ("fr_FR", u"French", u"Fran\xe7ais"),
497 ("it_IT", u"Italian", u"Italiano"),
498 ("km_KH", u"Khmer", u"\u1797\u17b6\u179f\u17b6\u1781\u17d2\u1798\u17c2\u179a"),
499 ("nl_NL", u"Dutch", u"Nederlands"),
500 ("pt_BR", u"Portuguese (Brazil)", u"Portugu\xeas (Brasil)"),
501 ("pt_PT", u"Portuguese (Portugal)", u"Portugu\xeas (Portugal)"),
502 ("ro_RO", u"Romanian", u"Rom\xe2n\u0103"),
503 ("ru_RU", u"Russian", u"\u0440\u0443\u0441\u0441\u043a\u0438\u0439"),
504 ("uk_UA", u"Ukrainian", u"\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430"),
505 ]
506
507 # Sort the list of locales by their English name.
508 LOCALE_NAMES.sort(key=lambda x: x[1])
509
510 def render(self, name=None, id=None, preselect=None):
511 return self.render_string("modules/select/locale.html",
512 name=name, id=id, preselect=preselect, supported_locales=self.LOCALE_NAMES)
513
514
515 class SelectTimezoneModule(UIModule):
516 def render(self, name=None, id=None, preselect=None):
517 return self.render_string("modules/select/timezone.html",
518 name=name, id=id, preselect=preselect,
519 supported_timezones=pytz.common_timezones)