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