]> git.ipfire.org Git - people/jschlag/pbs.git/blame - src/web/ui_modules.py
Merge branch 'master' of git://git.ipfire.org/pbs
[people/jschlag/pbs.git] / src / web / ui_modules.py
CommitLineData
f6e6ff79 1#!/usr/bin/python
9137135a 2
5669a87f
MT
3from __future__ import division
4
eedc6432 5import datetime
b9d096e0 6import itertools
5669a87f 7import math
cd870d0a 8import pytz
f6e6ff79
MT
9import re
10import string
11import tornado.escape
9137135a
MT
12import tornado.web
13
2c909128
MT
14from .. import users
15from ..constants import *
9137135a
MT
16
17class UIModule(tornado.web.UIModule):
18 @property
1aae140a 19 def backend(self):
4b9167ef 20 return self.handler.application.backend
9137135a 21
f6e6ff79
MT
22
23class TextModule(UIModule):
1aae140a
MT
24 BUGZILLA_PATTERN = re.compile(r"(?:bug\s?|#)(\d+)")
25 CVE_PATTERN = re.compile(r"(?:CVE)[\s\-](\d{4}\-\d{4})")
c764ce43 26
1aae140a 27 LINK = """<a href="%s" target="_blank" rel="noopener">%s</a>"""
c21cb4ec 28
b9d096e0 29 def split_paragraphs(self, s):
6ea360cc 30 for group_seperator, line_iteration in itertools.groupby(s.splitlines(True), key=str.isspace):
b9d096e0
MT
31 if group_seperator:
32 continue
33
34 paragraph = "".join(line_iteration)
35 yield paragraph.replace("\n", " ")
36
c764ce43 37 def render(self, text, pre=False, remove_linebreaks=True):
c764ce43
MT
38 if remove_linebreaks:
39 text = text.replace("\n", " ")
f6e6ff79 40
c764ce43
MT
41 # Escape the text and create make urls clickable.
42 text = tornado.escape.xhtml_escape(text)
43 text = tornado.escape.linkify(text, shorten=True,
1aae140a 44 extra_params="target=\"_blank\" rel=\"noopener\"")
f6e6ff79 45
1aae140a
MT
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)
f6e6ff79 48
c764ce43 49 # Search for CVE numbers and create hyperlinks.
1aae140a 50 text = re.sub(self.CVE_PATTERN, self._cve_repl, text, re.I|re.U)
f6e6ff79
MT
51
52 if pre:
c764ce43 53 return "<pre>%s</pre>" % text
f6e6ff79 54
9d0c3e75 55 return text
f6e6ff79 56
1aae140a
MT
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
f6e6ff79 68
b9d096e0
MT
69class 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
f6e6ff79
MT
79class ModalModule(UIModule):
80 def render(self, what, **kwargs):
81 what = "modules/modal-%s.html" % what
9137135a 82
f6e6ff79
MT
83 return self.render_string(what, **kwargs)
84
85
86class BuildHeadlineModule(UIModule):
d3a9ee4e 87 def render(self, build, short=False, shorter=False):
f6e6ff79
MT
88 if shorter:
89 short = True
90
91 return self.render_string("modules/build-headline.html",
d3a9ee4e 92 build=build, pkg=build.pkg, short=short, shorter=shorter)
f6e6ff79
MT
93
94
eedc6432
MT
95class JobsStatusModule(UIModule):
96 def render(self, build):
97 return self.render_string("modules/jobs/status.html",
98 build=build, jobs=build.jobs)
99
100
f6e6ff79
MT
101class BugsTableModule(UIModule):
102 def render(self, pkg, bugs):
103 return self.render_string("modules/bugs-table.html",
104 pkg=pkg, bugs=bugs)
105
106
4b1e87c4
MT
107class ChangelogModule(UIModule):
108 def render(self, name=None, builds=None, *args, **kwargs):
109 if not builds:
0c699ed0 110 builds = self.backend.builds.get_changelog(name, *args, **kwargs)
4b1e87c4
MT
111
112 return self.render_string("modules/changelog/index.html", builds=builds)
113
114
115class ChangelogEntryModule(UIModule):
116 def render(self, build):
117 return self.render_string("modules/changelog/entry.html", build=build)
118
119
f6e6ff79
MT
120class 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
127class FooterModule(UIModule):
128 def render(self):
129 return self.render_string("modules/footer.html")
130
131
8b713cc7
MT
132class 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
f6e6ff79
MT
152class PackagesTableModule(UIModule):
153 def render(self, job, packages):
154 return self.render_string("modules/packages-table.html", job=job,
155 packages=packages)
9137135a
MT
156
157
5669a87f
MT
158class 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
9137135a
MT
192class PackageTable2Module(UIModule):
193 def render(self, packages):
194 return self.render_string("modules/package-table-detail.html",
195 packages=packages)
196
197
198class FilesTableModule(UIModule):
199 def render(self, files):
200 return self.render_string("modules/files-table.html", files=files)
201
202
f6e6ff79
MT
203class 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
209class PackageHeaderModule(UIModule):
210 def render(self, pkg):
211 return self.render_string("modules/package-header.html", pkg=pkg)
212
213
9137135a 214class PackageFilesTableModule(UIModule):
f6e6ff79
MT
215 def render(self, pkg, filelist):
216 return self.render_string("modules/packages-files-table.html",
217 pkg=pkg, filelist=filelist)
9137135a
MT
218
219
220class BuildTableModule(UIModule):
f6e6ff79
MT
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
eedc6432
MT
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)
f6e6ff79
MT
242
243
244class BuildStateWarningsModule(UIModule):
245 def render(self, build):
246 return self.render_string("modules/build-state-warnings.html", build=build)
247
248
3c7e0537
MT
249class 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
258class JobStateModule(UIModule):
eedc6432 259 def render(self, job, cls=None, show_arch=False, show_icon=False, plain=False):
3c7e0537
MT
260 state = job.state
261
262 _ = self.locale.translate
263 classes = []
264
c62cd4db 265 icon = None
3c7e0537
MT
266 if state == "aborted":
267 text = _("Aborted")
268 classes.append("muted")
20e70907 269 icon = "icon-warning-sign"
3c7e0537
MT
270
271 elif state == "dependency_error":
272 text = _("Dependency problem")
273 classes.append("text-warning")
20e70907 274 icon = "icon-random"
3c7e0537
MT
275
276 elif state == "dispatching":
277 text = _("Dispatching")
278 classes.append("text-info")
20e70907 279 icon = "icon-download-alt"
3c7e0537
MT
280
281 elif state == "failed":
282 text = _("Failed")
283 classes.append("text-error")
20e70907 284 icon = "icon-remove"
3c7e0537
MT
285
286 elif state == "finished":
287 text = _("Finished")
288 classes.append("text-success")
20e70907 289 icon = "icon-ok"
3c7e0537
MT
290
291 elif state == "new":
292 text = _("New")
293 classes.append("muted")
20e70907 294 icon = "icon-asterisk"
3c7e0537
MT
295
296 elif state == "pending":
297 text = _("Pending")
298 classes.append("muted")
20e70907 299 icon = "icon-time"
3c7e0537
MT
300
301 elif state == "running":
302 text = _("Running")
c62cd4db 303 classes.append("text-info")
20e70907
MT
304 icon = "icon-cogs"
305
306 elif state == "uploading":
307 text = _("Uploading")
3c7e0537 308 classes.append("text-info")
20e70907 309 icon = "icon-upload-alt"
3c7e0537
MT
310
311 # Return just the string, is state is unknown.
312 else:
313 text = _("Unknown: %s") % state
314 classes.append("muted")
315
eedc6432
MT
316 if plain:
317 return text
318
3c7e0537
MT
319 if cls:
320 classes.append(cls)
321
eedc6432 322 if show_arch:
e459cbba 323 text = job.arch
eedc6432 324
20e70907
MT
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)
3c7e0537
MT
329
330
f6e6ff79
MT
331class 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
340class JobsListModule(UIModule):
6e63ed49
MT
341 def render(self, jobs):
342 return self.render_string("modules/jobs/list.html", jobs=jobs)
9137135a
MT
343
344
345class RepositoryTableModule(UIModule):
346 def render(self, distro, repos):
347 return self.render_string("modules/repository-table.html",
348 distro=distro, repos=repos)
349
350
351class SourceTableModule(UIModule):
352 def render(self, distro, sources):
353 return self.render_string("modules/source-table.html",
354 distro=distro, sources=sources)
355
356
357class 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] = \
0c699ed0 366 self.backend.packages.get_by_id(comment.pkg_id)
9137135a
MT
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] = \
0c699ed0 375 self.backend.users.get_by_id(comment.user_id)
9137135a
MT
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
f6e6ff79
MT
383class LogModule(UIModule):
384 def render(self, entries, **args):
385 return self.render_string("modules/log.html",
386 entries=entries, args=args)
387
388
389class LogEntryModule(UIModule):
e434b018 390 def render(self, entry, small=None, **args):
7d0635e2 391 if small or not entry.user:
e434b018
MT
392 template = "modules/log-entry-small.html"
393 else:
394 template = "modules/log-entry.html"
395
62c7e7cd
MT
396 return self.render_string(template, entry=entry, u=entry.user,
397 show_build=False, **args)
f6e6ff79
MT
398
399
400class LogEntryCommentModule(LogEntryModule):
62c7e7cd 401 def render(self, entry, show_build=False, **args):
f6e6ff79 402 return self.render_string("modules/log-entry-comment.html",
62c7e7cd 403 entry=entry, u=entry.user, show_build=show_build, **args)
f6e6ff79
MT
404
405
406class MaintainerModule(UIModule):
407 def render(self, maintainer):
2c909128 408 if isinstance(maintainer, users.User):
f6e6ff79
MT
409 type = "user"
410 else:
411 type = "string"
412
413 return self.render_string("modules/maintainer.html",
414 type=type, maintainer=maintainer)
415
416
9137135a
MT
417class 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
432class 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:
0c699ed0 441 message["build"] = self.backend.builds.get_by_id(message.build_id)
9137135a
MT
442
443 elif message.pkg_id:
0c699ed0 444 message["pkg"] = self.backend.packages.get_by_id(message.pkg_id)
9137135a
MT
445
446 return self.render_string("modules/log-table.html",
447 messages=messages, links=links)
448
449
450class UsersTableModule(UIModule):
451 def render(self, users):
452 return self.render_string("modules/user-table.html", users=users)
453
454
455class BuildOffsetModule(UIModule):
456 def render(self):
457 return self.render_string("modules/build-offset.html")
458
459
460class 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)
f6e6ff79
MT
466
467
468class UpdatesTableModule(UIModule):
469 def render(self, updates):
470 return self.render_string("modules/updates-table.html", updates=updates)
471
472
473class 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)
cd870d0a
MT
483
484
485class SelectLocaleModule(UIModule):
b1eb6312 486 LOCALE_NAMES = [
cd870d0a
MT
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"),
d3bda196 499 ("nl_NL", u"Dutch", u"Nederlands"),
cd870d0a
MT
500 ("pt_BR", u"Portuguese (Brazil)", u"Portugu\xeas (Brasil)"),
501 ("pt_PT", u"Portuguese (Portugal)", u"Portugu\xeas (Portugal)"),
b5321ec9 502 ("ro_RO", u"Romanian", u"Rom\xe2n\u0103"),
cd870d0a 503 ("ru_RU", u"Russian", u"\u0440\u0443\u0441\u0441\u043a\u0438\u0439"),
b1eb6312
MT
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])
cd870d0a
MT
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
515class 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)