]> git.ipfire.org Git - pbs.git/blame - src/web/ui_modules.py
bootstrap4: port job boxe to bootstrap4
[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
5669a87f 6import math
cd870d0a 7import pytz
f6e6ff79 8import re
9137135a
MT
9import tornado.web
10
2c909128
MT
11from .. import users
12from ..constants import *
9137135a
MT
13
14class UIModule(tornado.web.UIModule):
15 @property
1aae140a 16 def backend(self):
4b9167ef 17 return self.handler.application.backend
9137135a 18
f6e6ff79
MT
19
20class TextModule(UIModule):
1aae140a
MT
21 BUGZILLA_PATTERN = re.compile(r"(?:bug\s?|#)(\d+)")
22 CVE_PATTERN = re.compile(r"(?:CVE)[\s\-](\d{4}\-\d{4})")
c764ce43 23
1aae140a 24 LINK = """<a href="%s" target="_blank" rel="noopener">%s</a>"""
c21cb4ec 25
cf28d42e 26 def render(self, text):
3cf4f987
MT
27 # Handle empty messages
28 if not text:
29 return ""
30
1aae140a
MT
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)
f6e6ff79 33
c764ce43 34 # Search for CVE numbers and create hyperlinks.
1aae140a 35 text = re.sub(self.CVE_PATTERN, self._cve_repl, text, re.I|re.U)
f6e6ff79 36
f78cbba4 37 return self.render_string("modules/text.html", paragraphs=text.split("\n\n"))
f6e6ff79 38
1aae140a
MT
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
f6e6ff79 50
c6e799b4
MT
51class CommitMessageModule(UIModule):
52 def render(self, commit):
53 return self.render_string("modules/commit-message.html", commit=commit)
b9d096e0
MT
54
55
f6e6ff79
MT
56class ModalModule(UIModule):
57 def render(self, what, **kwargs):
58 what = "modules/modal-%s.html" % what
9137135a 59
f6e6ff79
MT
60 return self.render_string(what, **kwargs)
61
62
63class BuildHeadlineModule(UIModule):
d3a9ee4e 64 def render(self, build, short=False, shorter=False):
f6e6ff79
MT
65 if shorter:
66 short = True
67
68 return self.render_string("modules/build-headline.html",
d3a9ee4e 69 build=build, pkg=build.pkg, short=short, shorter=shorter)
f6e6ff79
MT
70
71
eedc6432
MT
72class JobsStatusModule(UIModule):
73 def render(self, build):
74 return self.render_string("modules/jobs/status.html",
75 build=build, jobs=build.jobs)
76
77
f6e6ff79
MT
78class BugsTableModule(UIModule):
79 def render(self, pkg, bugs):
80 return self.render_string("modules/bugs-table.html",
81 pkg=pkg, bugs=bugs)
82
83
4b1e87c4
MT
84class ChangelogModule(UIModule):
85 def render(self, name=None, builds=None, *args, **kwargs):
86 if not builds:
0c699ed0 87 builds = self.backend.builds.get_changelog(name, *args, **kwargs)
4b1e87c4
MT
88
89 return self.render_string("modules/changelog/index.html", builds=builds)
90
91
92class ChangelogEntryModule(UIModule):
93 def render(self, build):
94 return self.render_string("modules/changelog/entry.html", build=build)
95
96
f6e6ff79
MT
97class 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
104class FooterModule(UIModule):
105 def render(self):
106 return self.render_string("modules/footer.html")
107
108
8b713cc7
MT
109class 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
f6e6ff79
MT
129class PackagesTableModule(UIModule):
130 def render(self, job, packages):
131 return self.render_string("modules/packages-table.html", job=job,
132 packages=packages)
9137135a
MT
133
134
5669a87f
MT
135class 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
9137135a
MT
169class PackageTable2Module(UIModule):
170 def render(self, packages):
171 return self.render_string("modules/package-table-detail.html",
172 packages=packages)
173
174
175class FilesTableModule(UIModule):
176 def render(self, files):
177 return self.render_string("modules/files-table.html", files=files)
178
179
f6e6ff79
MT
180class 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
186class PackageHeaderModule(UIModule):
187 def render(self, pkg):
188 return self.render_string("modules/package-header.html", pkg=pkg)
189
190
9137135a 191class PackageFilesTableModule(UIModule):
f6e6ff79
MT
192 def render(self, pkg, filelist):
193 return self.render_string("modules/packages-files-table.html",
194 pkg=pkg, filelist=filelist)
9137135a
MT
195
196
197class BuildTableModule(UIModule):
f6e6ff79
MT
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
eedc6432
MT
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)
f6e6ff79
MT
219
220
221class BuildStateWarningsModule(UIModule):
222 def render(self, build):
223 return self.render_string("modules/build-state-warnings.html", build=build)
224
d685bd2c
JS
225class 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
f6e6ff79 244
3c7e0537
MT
245class 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
254class JobStateModule(UIModule):
eedc6432 255 def render(self, job, cls=None, show_arch=False, show_icon=False, plain=False):
3c7e0537
MT
256 state = job.state
257
258 _ = self.locale.translate
259 classes = []
260
330a67f3
JS
261 classes.append("badge")
262
c62cd4db 263 icon = None
3c7e0537
MT
264 if state == "aborted":
265 text = _("Aborted")
330a67f3 266 classes.append("badge-secondary")
20e70907 267 icon = "icon-warning-sign"
3c7e0537 268
3c7e0537
MT
269 elif state == "dispatching":
270 text = _("Dispatching")
330a67f3 271 classes.append("badge-info")
20e70907 272 icon = "icon-download-alt"
3c7e0537
MT
273
274 elif state == "failed":
275 text = _("Failed")
330a67f3 276 classes.append("badge-danger")
20e70907 277 icon = "icon-remove"
3c7e0537
MT
278
279 elif state == "finished":
280 text = _("Finished")
330a67f3 281 classes.append("badge-success")
20e70907 282 icon = "icon-ok"
3c7e0537 283
3c7e0537
MT
284 elif state == "pending":
285 text = _("Pending")
330a67f3 286 classes.append("badge-secondary")
20e70907 287 icon = "icon-time"
3c7e0537
MT
288
289 elif state == "running":
290 text = _("Running")
330a67f3 291 classes.append("badge-info")
20e70907
MT
292 icon = "icon-cogs"
293
294 elif state == "uploading":
295 text = _("Uploading")
330a67f3 296 classes.append("badge-info")
20e70907 297 icon = "icon-upload-alt"
3c7e0537
MT
298
299 # Return just the string, is state is unknown.
300 else:
301 text = _("Unknown: %s") % state
330a67f3 302 classes.append("text-muted")
3c7e0537 303
eedc6432
MT
304 if plain:
305 return text
306
3c7e0537
MT
307 if cls:
308 classes.append(cls)
309
eedc6432 310 if show_arch:
e459cbba 311 text = job.arch
eedc6432 312
20e70907
MT
313 if show_icon and icon:
314 text = """<i class="%s"></i> %s""" % (icon, text)
315
316 return """<span class="%s">%s</span>""" % (" ".join(classes), text)
3c7e0537
MT
317
318
f6e6ff79
MT
319class JobsTableModule(UIModule):
320 def render(self, build, jobs=None, type="release"):
321 if jobs is None:
322 jobs = build.jobs
323
324 return self.render_string("modules/jobs-table.html", build=build,
325 jobs=jobs, type=type)
326
327
328class JobsListModule(UIModule):
6e63ed49
MT
329 def render(self, jobs):
330 return self.render_string("modules/jobs/list.html", jobs=jobs)
9137135a
MT
331
332
333class RepositoryTableModule(UIModule):
334 def render(self, distro, repos):
335 return self.render_string("modules/repository-table.html",
336 distro=distro, repos=repos)
337
338
339class SourceTableModule(UIModule):
340 def render(self, distro, sources):
341 return self.render_string("modules/source-table.html",
342 distro=distro, sources=sources)
343
344
345class CommentsTableModule(UIModule):
346 def render(self, comments, show_package=False, show_user=True):
347 pkgs, users = {}, {}
348 for comment in comments:
349 if show_package:
350 try:
351 pkg = pkgs[comment.pkg_id]
352 except KeyError:
353 pkg = pkgs[comment.pkg_id] = \
0c699ed0 354 self.backend.packages.get_by_id(comment.pkg_id)
9137135a
MT
355
356 comment["pkg"] = pkg
357
358 if show_user:
359 try:
360 user = users[comment.user_id]
361 except KeyError:
362 user = users[comment.user_id] = \
0c699ed0 363 self.backend.users.get_by_id(comment.user_id)
9137135a
MT
364
365 comment["user"] = user
366
367 return self.render_string("modules/comments-table.html",
368 comments=comments, show_package=show_package, show_user=show_user)
369
370
f6e6ff79
MT
371class LogModule(UIModule):
372 def render(self, entries, **args):
373 return self.render_string("modules/log.html",
374 entries=entries, args=args)
375
376
377class LogEntryModule(UIModule):
e434b018 378 def render(self, entry, small=None, **args):
7d0635e2 379 if small or not entry.user:
e434b018
MT
380 template = "modules/log-entry-small.html"
381 else:
382 template = "modules/log-entry.html"
383
62c7e7cd
MT
384 return self.render_string(template, entry=entry, u=entry.user,
385 show_build=False, **args)
f6e6ff79
MT
386
387
388class LogEntryCommentModule(LogEntryModule):
62c7e7cd 389 def render(self, entry, show_build=False, **args):
f6e6ff79 390 return self.render_string("modules/log-entry-comment.html",
62c7e7cd 391 entry=entry, u=entry.user, show_build=show_build, **args)
f6e6ff79
MT
392
393
8dc6f5a4
MT
394class LinkToUserModule(UIModule):
395 def render(self, user):
396 return self.render_string("modules/link-to-user.html", user=user, users=users)
f6e6ff79
MT
397
398
9137135a
MT
399class BuildLogModule(UIModule):
400 # XXX deprecated
401 def render(self, messages):
402 _ = self.locale.translate
403
404 for message in messages:
405 try:
406 msg = LOG2MSG[message.message]
407 message["message"] = _(msg)
408 except KeyError:
409 pass
410
411 return self.render_string("modules/build-log.html", messages=messages)
412
413
414class LogTableModule(UIModule):
415 def render(self, messages, links=["pkg",]):
416 for message in messages:
417 try:
418 message["message"] = LOG2MSG[message.message]
419 except KeyError:
420 pass
421
422 if message.build_id:
0c699ed0 423 message["build"] = self.backend.builds.get_by_id(message.build_id)
9137135a
MT
424
425 elif message.pkg_id:
0c699ed0 426 message["pkg"] = self.backend.packages.get_by_id(message.pkg_id)
9137135a
MT
427
428 return self.render_string("modules/log-table.html",
429 messages=messages, links=links)
430
431
432class UsersTableModule(UIModule):
433 def render(self, users):
434 return self.render_string("modules/user-table.html", users=users)
435
436
437class BuildOffsetModule(UIModule):
438 def render(self):
439 return self.render_string("modules/build-offset.html")
440
441
442class RepoActionsTableModule(UIModule):
443 def render(self, repo):
444 actions = repo.get_actions()
445
446 return self.render_string("modules/repo-actions-table.html",
447 repo=repo, actions=actions)
f6e6ff79
MT
448
449
450class UpdatesTableModule(UIModule):
451 def render(self, updates):
452 return self.render_string("modules/updates-table.html", updates=updates)
453
454
455class WatchersSidebarTableModule(UIModule):
456 def css_files(self):
457 return "css/watchers-sidebar-table.css"
458
459 def render(self, build, watchers, limit=5):
460 # Sort the watchers by their realname.
461 watchers.sort(key=lambda watcher: watcher.realname)
462
463 return self.render_string("modules/watchers-sidebar-table.html",
464 build=build, watchers=watchers, limit=limit)
cd870d0a
MT
465
466
467class SelectLocaleModule(UIModule):
b1eb6312 468 LOCALE_NAMES = [
cd870d0a
MT
469 # local code, English name, name
470 ("ca_ES", u"Catalan", "Catal\xc3\xa0"),
471 ("da_DK", u"Danish", u"Dansk"),
472 ("de_DE", u"German", u"Deutsch"),
473 ("en_GB", u"English (UK)", u"English (UK)"),
474 ("en_US", u"English (US)", u"English (US)"),
475 ("es_ES", u"Spanish (Spain)", u"Espa\xf1ol (Espa\xf1a)"),
476 ("es_LA", u"Spanish", u"Espa\xf1ol"),
477 ("fr_CA", u"French (Canada)", u"Fran\xe7ais (Canada)"),
478 ("fr_FR", u"French", u"Fran\xe7ais"),
479 ("it_IT", u"Italian", u"Italiano"),
480 ("km_KH", u"Khmer", u"\u1797\u17b6\u179f\u17b6\u1781\u17d2\u1798\u17c2\u179a"),
d3bda196 481 ("nl_NL", u"Dutch", u"Nederlands"),
cd870d0a
MT
482 ("pt_BR", u"Portuguese (Brazil)", u"Portugu\xeas (Brasil)"),
483 ("pt_PT", u"Portuguese (Portugal)", u"Portugu\xeas (Portugal)"),
b5321ec9 484 ("ro_RO", u"Romanian", u"Rom\xe2n\u0103"),
cd870d0a 485 ("ru_RU", u"Russian", u"\u0440\u0443\u0441\u0441\u043a\u0438\u0439"),
b1eb6312
MT
486 ("uk_UA", u"Ukrainian", u"\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430"),
487 ]
488
489 # Sort the list of locales by their English name.
490 LOCALE_NAMES.sort(key=lambda x: x[1])
cd870d0a
MT
491
492 def render(self, name=None, id=None, preselect=None):
493 return self.render_string("modules/select/locale.html",
494 name=name, id=id, preselect=preselect, supported_locales=self.LOCALE_NAMES)
495
496
497class SelectTimezoneModule(UIModule):
498 def render(self, name=None, id=None, preselect=None):
499 return self.render_string("modules/select/timezone.html",
500 name=name, id=id, preselect=preselect,
501 supported_timezones=pytz.common_timezones)