From 9a3c11b09f18e44604b9f7bded848929fced8397 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Fri, 10 Dec 2021 16:09:57 +0100 Subject: [PATCH] [IMP] runbot: use custom layout and improve views Runbot layout modifies the website/portal base layout to remove navbar, footer, overides some custom styles. A lot of assets are loaded but not used. The only real usefull elements are base assets (bootstrap, ...) and the login button. Migrating to the next version of odoo is usually painfull because some xpath may break, extra element added, or some style change may break the page, needing to add more and more xpath, css rules, ... for very little benefits. This cleanup creates a custom base layout for runbot independant from base odoo templates. Also add a breadcrumb, navigation arrow, and improve batch links --- runbot/__manifest__.py | 3 +- runbot/controllers/frontend.py | 28 ++- runbot/data/runbot_data.xml | 22 +- runbot/models/batch.py | 3 +- runbot/models/build.py | 19 +- runbot/models/bundle.py | 5 + runbot/models/runbot.py | 2 +- runbot/static/src/css/runbot.css | 323 ++++++++++++++++++++++++ runbot/static/src/css/runbot.scss | 255 ------------------- runbot/static/src/js/runbot.js | 50 ++-- runbot/templates/assets.xml | 11 - runbot/templates/batch.xml | 10 +- runbot/templates/branch.xml | 2 +- runbot/templates/build.xml | 351 ++++++++++++++------------- runbot/templates/build_error.xml | 4 +- runbot/templates/build_stats.xml | 4 +- runbot/templates/bundle.xml | 2 +- runbot/templates/commit.xml | 2 +- runbot/templates/dashboard.xml | 4 +- runbot/templates/frontend.xml | 24 +- runbot/templates/utils.xml | 275 +++++++++++---------- runbot_populate/demo/runbot_demo.xml | 1 - runbot_populate/models/runbot.py | 2 +- 23 files changed, 751 insertions(+), 651 deletions(-) create mode 100644 runbot/static/src/css/runbot.css delete mode 100644 runbot/static/src/css/runbot.scss delete mode 100644 runbot/templates/assets.xml diff --git a/runbot/__manifest__.py b/runbot/__manifest__.py index 5d3c77ac..4b5acb53 100644 --- a/runbot/__manifest__.py +++ b/runbot/__manifest__.py @@ -22,7 +22,7 @@ 'security/ir.model.access.csv', 'security/ir.rule.csv', - 'templates/assets.xml', + 'templates/utils.xml', 'templates/badge.xml', 'templates/batch.xml', 'templates/branch.xml', @@ -34,7 +34,6 @@ 'templates/frontend.xml', 'templates/git.xml', 'templates/nginx.xml', - 'templates/utils.xml', 'templates/build_error.xml', 'views/branch_views.xml', diff --git a/runbot/controllers/frontend.py b/runbot/controllers/frontend.py index 97434cad..2a20dade 100644 --- a/runbot/controllers/frontend.py +++ b/runbot/controllers/frontend.py @@ -53,6 +53,7 @@ def route(routes, **kw): response.qcontext['current_path'] = request.httprequest.full_path response.qcontext['refresh'] = refresh response.qcontext['filter_mode'] = filter_mode + response.qcontext['default_category'] = request.env['ir.model.data'].xmlid_to_res_id('runbot.default_category') response.qcontext['qu'] = QueryURL('/runbot/%s' % (slug(project)), path_args=['search'], search=search, refresh=refresh) if 'title' not in response.qcontext: response.qcontext['title'] = 'Runbot %s' % project.name or '' @@ -271,23 +272,35 @@ class Runbot(Controller): elif operation == 'wakeup': build._wake_up() - return werkzeug.utils.redirect(build.build_url) + return str(build.id) - @route(['/runbot/build/'], type='http', auth="public", website=True, sitemap=False) - def build(self, build_id, search=None, **post): + @route([ + '/runbot/build/', + '/runbot/batch//build/' + ], type='http', auth="public", website=True, sitemap=False) + def build(self, build_id, search=None, from_batch=None, **post): """Events/Logs""" - Build = request.env['runbot.build'] + if from_batch: + from_batch = request.env['runbot.batch'].browse(int(from_batch)) + from_batch = from_batch.with_context(batch=from_batch) + Build = request.env['runbot.build'].with_context(batch=from_batch) build = Build.browse([build_id])[0] if not build.exists(): return request.not_found() - + siblings = (build.parent_id.children_ids if build.parent_id else from_batch.slot_ids.build_id if from_batch else build).sorted('id') context = { 'build': build, - 'default_category': request.env['ir.model.data'].xmlid_to_res_id('runbot.default_category'), + 'from_batch': from_batch, 'project': build.params_id.trigger_id.project_id, - 'title': 'Build %s' % build.id + 'title': 'Build %s' % build.id, + 'siblings': siblings, + # following logic is not the most efficient but good enough + 'prev_ko': next((b for b in reversed(siblings) if b.id < build.id and b.global_result != 'ok'), Build), + 'prev_bu': next((b for b in reversed(siblings) if b.id < build.id), Build), + 'next_bu': next((b for b in siblings if b.id > build.id), Build), + 'next_ko': next((b for b in siblings if b.id > build.id and b.global_result != 'ok'), Build), } return request.render("runbot.build", context) @@ -426,7 +439,6 @@ class Runbot(Controller): context = { 'build': build, 'build_stats': build_stats, - 'default_category': request.env['ir.model.data'].xmlid_to_res_id('runbot.default_category'), 'project': build.params_id.trigger_id.project_id, 'title': 'Build %s statistics' % build.id } diff --git a/runbot/data/runbot_data.xml b/runbot/data/runbot_data.xml index 9debe328..c43885b8 100644 --- a/runbot/data/runbot_data.xml +++ b/runbot/data/runbot_data.xml @@ -16,16 +16,18 @@ R&D - - master - True - - - - Dummy - True - - + + + master + True + + + + Dummy + True + + + runbot.runbot_is_base_regex diff --git a/runbot/models/batch.py b/runbot/models/batch.py index 8df69ee9..9e187222 100644 --- a/runbot/models/batch.py +++ b/runbot/models/batch.py @@ -51,8 +51,7 @@ class Batch(models.Model): def _url(self): self.ensure_one() - runbot_domain = self.env['runbot.runbot']._domain() - return "http://%s/runbot/batch/%s" % (runbot_domain, self.id) + return "/runbot/batch/%s" % self.id def _new_commit(self, branch, match_type='new'): # if not the same hash for repo: diff --git a/runbot/models/build.py b/runbot/models/build.py index f3262197..beaa5d11 100644 --- a/runbot/models/build.py +++ b/runbot/models/build.py @@ -142,6 +142,7 @@ class BuildResult(models.Model): description = fields.Char('Description', help='Informative description') md_description = fields.Char(compute='_compute_md_description', String='MD Parsed Description', help='Informative description markdown parsed') + display_name = fields.Char(compute='_compute_display_name') # Related fields for convenience version_id = fields.Many2one('runbot.version', related='params_id.version_id', store=True, index=True) @@ -201,6 +202,7 @@ class BuildResult(models.Model): parent_id = fields.Many2one('runbot.build', 'Parent Build', index=True) parent_path = fields.Char('Parent path', index=True) top_parent = fields.Many2one('runbot.build', compute='_compute_top_parent') + ancestors = fields.Many2many('runbot.build', compute='_compute_ancestors') # should we add a has children stored boolean? children_ids = fields.One2many('runbot.build', 'parent_id') @@ -220,6 +222,11 @@ class BuildResult(models.Model): static_run = fields.Char('Static run URL') + @api.depends('description', 'params_id.config_id') + def _compute_display_name(self): + for build in self: + build.display_name = build.description or build.config_id.name + @api.depends('params_id.config_id') def _compute_log_list(self): # storing this field because it will be access trhoug repo viewn and keep track of the list at create for build in self: @@ -260,6 +267,10 @@ class BuildResult(models.Model): for build in self: build.top_parent = self.browse(int(build.parent_path.split('/')[0])) + def _compute_ancestors(self): + for build in self: + build.ancestors = self.browse([int(b) for b in build.parent_path.split('/') if b]) + def _get_youngest_state(self, states): index = min([self._get_state_score(state) for state in states]) return state_order[index] @@ -379,9 +390,15 @@ class BuildResult(models.Model): else: build.domain = "%s:%s" % (domain, build.port) + @api.depends_context('batch') def _compute_build_url(self): + batch = self.env.context.get('batch') + print(self.env.context) for build in self: - build.build_url = "/runbot/build/%s" % build.id + if batch: + build.build_url = "/runbot/batch/%s/build/%s" % (batch.id, build.id) + else: + build.build_url = "/runbot/build/%s" % build.id @api.depends('job_start', 'job_end') def _compute_job_time(self): diff --git a/runbot/models/bundle.py b/runbot/models/bundle.py index 575919ba..693dd36d 100644 --- a/runbot/models/bundle.py +++ b/runbot/models/bundle.py @@ -184,6 +184,11 @@ class Bundle(models.Model): for batch in batchs: batch.bundle_id.last_done_batch = batch + def _url(self): + self.ensure_one() + return "/runbot/bundle/%s" % self.id + + def create(self, values_list): res = super().create(values_list) if res.is_base: diff --git a/runbot/models/runbot.py b/runbot/models/runbot.py index 833ee22a..c99d5fec 100644 --- a/runbot/models/runbot.py +++ b/runbot/models/runbot.py @@ -146,7 +146,7 @@ class Runbot(models.AbstractModel): return self.env.cr.fetchall() def _domain(self): - return self.env.get('ir.config_parameter').get_param('runbot.runbot_domain', fqdn()) + return self.env.get('ir.config_parameter').sudo().get_param('runbot.runbot_domain', fqdn()) def _reload_nginx(self): env = self.env diff --git a/runbot/static/src/css/runbot.css b/runbot/static/src/css/runbot.css new file mode 100644 index 00000000..31ba779b --- /dev/null +++ b/runbot/static/src/css/runbot.css @@ -0,0 +1,323 @@ +body { + margin: 0; + font-size: 0.875rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: white; +} + +form { + margin: 0; +} + +table { + font-size: 0.875rem; +} + +.fa { + line-height: inherit; /* reset fa icon line height to body height*/ +} + +a { + color: #00A09D; + text-decoration: none; +} + +a:hover { + color: #005452; + text-decoration: underline; +} + + +.breadcrumb-item.active a { + color: #6c757d; +} + +.breadcrumb { + background-color: inherit; + margin-bottom: 0; +} + +.build_details { + padding: 5px; +} + +.separator { + border-top: 2px solid #666; +} + +[data-toggle="collapse"] .fa:before { + content: "\f139"; +} + +[data-toggle="collapse"].collapsed .fa:before { + content: "\f13a"; +} + +body, .table { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #444; +} + +.btn-default { + background-color: #fff; + color: #444; + border-color: #ccc; +} + +.btn-default:hover { + background-color: #ccc; + color: #444; + border-color: #ccc; +} + +.btn-sm, .btn-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.89rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +.btn-ssm, .btn-group-ssm > .btn { + padding: 0.22rem 0.4rem; + font-size: 0.82rem; + line-height: 1; + border-radius: 0.2rem; +} + +.killed, .bg-killed, .bg-killed-light { + background-color: #aaa; +} + +.dropdown-toggle:after { + content: none; +} + +.one_line { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.batch_tile { + padding: 6px; +} + +.branch_time { + float: right; + margin-left: 10px; +} + +:root { + --info-light: #d9edf7; +} + +.bg-success-light { + background-color: #dff0d8; +} + +.bg-danger-light { + background-color: #f2dede; +} + +.bg-info-light { + background-color: var(--info-light); +} + +.bg-warning-light { + background-color: #fff9e6; +} + +.text-info { + color: #096b72 !important; +} + +.build_subject_buttons { + display: flex; +} + +.build_buttons { + margin-left: auto; +} + +.bg-killed { + background-color: #aaa; +} + +.badge-killed { + background-color: #aaa; +} + +.table-condensed td { + padding: 0.25rem; +} + +.line-through { + text-decoration: line-through; +} + +.badge-light { + border: 1px solid #AAA; +} + +.slot_button_group { + display: flex; + padding: 0 1px; +} + +.slot_button_group .btn { + flex: 0 0 25px; +} + +.slot_button_group .btn.slot_name { + width: 40px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1 1 auto; + text-align: left; +} + +.batch_header { + padding: 6px; +} + +.batch_header:hover { + background-color: rgba(0, 0, 0, 0.1) +} + +.header_hover { + visibility: hidden; +} + +.batch_header:hover .header_hover { + visibility: visible; +} + +.batch_slots { + display: flex; + flex-wrap: wrap; + padding: 6px; +} + +.batch_commits { + background-color: white; +} + +.batch_commits { + padding: 2px; +} + +.match_type_new { + background-color: var(--info-light); +} + +.batch_row .slot_container { + flex: 1 0 200px; + padding: 0 4px; +} + +.batch_row .slot_filler { + width: 100px; + height: 0px; + flex: 1 0 200px; + padding: 0 4px; +} + +.bundle_row { + border-bottom: 1px solid var(--gray); +} + +.bundle_row .batch_commits { + font-size: 80%; +} + +.bundle_row .slot_container { + flex: 1 0 50%; +} + +.bundle_row .slot_filler { + flex: 1 0 50%; +} + +.bundle_row .more .batch_commits { + display: block; +} + +/*.bundle_row .nomore .batch_commits { + display: none; + padding: 8px; +} + +.bundle_row .nomore.batch_tile:hover .batch_commits { + display: block; + position: absolute; + bottom: 1px; + transform: translateY(100%); + z-index: 100; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.2rem; + box-sizing: border-box; + margin-left: -1px; +}*/ + +.chart-legend { + max-height: calc(100vh - 160px); + overflow-y: scroll; + overflow-x: hidden; + cursor: pointer; + padding: 5px; +} + +.chart-legend .label { + margin-left: 5px; + font-weight: bold; +} + +.chart-legend .disabled .color { + visibility: hidden; +} + +.chart-legend .disabled .label { + font-weight: normal; + text-decoration: line-through; + margin-left: 5px; +} + +.chart-legend ul { + list-style-type: none; + margin: 0; + padding: 0; +} + +.limited-height { + max-height: 180px; + overflow: scroll; + -ms-overflow-style: none; + scrollbar-width: none; +} + +.limited-height > hr { + margin: 2px 0px; +} + +.limited-height:before { + content: ''; + width: 100%; + height: 30px; + position: absolute; + left: 0; + bottom: 0; + background: linear-gradient(transparent 0px, white 27px); +} + +.limited-height::-webkit-scrollbar { + display: none; +} + +.limited-height-toggle:hover { + background-color: #DDD; +} + diff --git a/runbot/static/src/css/runbot.scss b/runbot/static/src/css/runbot.scss deleted file mode 100644 index 88e6f784..00000000 --- a/runbot/static/src/css/runbot.scss +++ /dev/null @@ -1,255 +0,0 @@ -.separator { - border-top: 2px solid #666; -} - -[data-toggle="collapse"] .fa:before { - content: "\f139"; -} - -[data-toggle="collapse"].collapsed .fa:before { - content: "\f13a"; -} - -body, .table{ - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - color:#444; -} - -.btn-default { - background-color: #fff; - color: #444; - border-color: #ccc; -} - -.btn-default:hover { - background-color: #ccc; - color: #444; - border-color: #ccc; -} - -.btn-sm, .btn-group-sm > .btn { - padding: 0.25rem 0.5rem; - font-size: 0.89rem; - line-height: 1.5; - border-radius: 0.2rem; -} -.btn-ssm, .btn-group-ssm > .btn { - padding: 0.22rem 0.4rem; - font-size: 0.82rem; - line-height: 1; - border-radius: 0.2rem; -} - -.killed, .bg-killed, .bg-killed-light { - background-color: #aaa; -} - -.dropdown-toggle:after { content: none } - -.one_line { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.batch_tile { - padding: 6px; -} -.branch_time { - float:right; - margin-left:10px; -} - - -:root { - --info-light: #d9edf7; -} - - -.bg-success-light { - background-color: #dff0d8; -} -.bg-danger-light { - background-color: #f2dede; -} -.bg-info-light { - background-color: var(--info-light); -} - -.text-info{ - color: #096b72 !important; -} -.build_subject_buttons { - display: flex; -} -.build_buttons { - margin-left: auto -} - -.bg-killed { - background-color: #aaa; -} - -.badge-killed { - background-color: #aaa; -} - -.table-condensed td { - padding: 0.25rem; -} - -.line-through { - text-decoration: line-through; -} - -.badge-light{ - border: 1px solid #AAA; -} -.arrow{ - display: none; -} - -.badge-light:hover .arrow{ - display: inline; -} - -.slot_button_group { - display: flex; - padding: 0 1px; -} - -.slot_button_group .btn { - flex: 0 0 25px; -} - -.slot_button_group .btn.slot_name { - width: 40px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1 1 auto; - text-align: left; -} - -.batch_header { - padding: 6px; - padding-bottom: 0px; -} - -.batch_slots { - display: flex; - flex-wrap: wrap; - padding: 6px; -} - -.batch_commits { - background-color: white; -} - -.batch_commits { - padding: 2px; -} - -.match_type_new { - background-color: var(--info-light); -} -.batch_row { - .slot_container{ - flex: 1 0 200px; - padding: 0 4px; - } - .slot_filler { - width: 100px; - height: 0px; - flex: 1 0 200px; - padding: 0 4px; - } -} -.bundle_row { - border-bottom: 1px solid var(--gray); - .batch_commits { - font-size: 80%; - } - .slot_container{ - flex:1 0 50%; - } - .slot_filler { - flex:1 0 50%; - } - .more { - .batch_commits { - display: block; - } - } - .nomore { - .batch_commits { - display: none; - padding:8px; - } - } - .nomore.batch_tile:hover { - .batch_commits { - display: block; - position: absolute; - bottom: 1px; - transform: translateY(100%); - z-index: 100; - border: 1px solid rgba(0, 0, 0, 0.125); - border-radius: 0.2rem; - box-sizing: border-box; - margin-left:-1px; - } - } -} -.chart-legend { - max-height: calc(100vh - 160px); - overflow-y: scroll; - overflow-x: hidden; - cursor: pointer; - padding: 5px; - - .label { - margin-left: 5px; - font-weight: bold; - } - .disabled{ - .color { - visibility: hidden; - } - .label { - font-weight: normal; - text-decoration: line-through; - margin-left: 5px; - } - } - - ul { - list-style-type: none; - margin: 0; - padding: 0; - } -} -.limited-height { - max-height: 180px; - overflow: scroll; - - >hr { - margin: 2px 0px; - } - &:before { - content:''; - width:100%; - height:30px; - position:absolute; - left:0; - bottom:0; - background:linear-gradient(transparent 0px, white 27px); - } - -ms-overflow-style: none; - scrollbar-width: none; -} -.limited-height::-webkit-scrollbar { - display: none; -} -.limited-height-toggle:hover { - background-color: #DDD; -} diff --git a/runbot/static/src/js/runbot.js b/runbot/static/src/js/runbot.js index 49ffee50..fe09e18d 100644 --- a/runbot/static/src/js/runbot.js +++ b/runbot/static/src/js/runbot.js @@ -1,52 +1,32 @@ (function($) { - "use strict"; - - var OPMAP = { - 'rebuild': {operation: 'rebuild', then: 'redirect'}, - 'kill': {operation: 'kill', then: 'reload'}, - 'wakeup': {operation: 'wakeup', then: 'reload'} - }; - + "use strict"; $(function () { $(document).on('click', '[data-runbot]', function (e) { e.preventDefault(); - var data = $(this).data(); - var segment = OPMAP[data.runbot]; - if (!segment) { return; } - - // window.location.pathname but compatibility is iffy - var currentPath = window.location.href.replace(window.location.protocol + '//' + window.location.host, '').split('?')[0]; - var buildPath = _.str.sprintf('/runbot/build/%s', data.runbotBuild); - // no responseURL on $.ajax so use native object + var operation = data.runbot; + if (!operation) { + return; + } var xhr = new XMLHttpRequest(); xhr.addEventListener('load', function () { - switch (segment.then) { - case 'redirect': - if (currentPath === buildPath && xhr.responseURL) { - window.location.href = xhr.responseURL; - break; - } - // fallthrough to reload if no responseURL or we're - // not on the build's page - case 'reload': + if (operation == 'rebuild' && window.location.href.split('?')[0].endsWith('/build/' + data.runbotBuild)){ + window.location.href = window.location.href.replace('/build/' + data.runbotBuild, '/build/' + xhr.responseText); + } else { window.location.reload(); - break; } }); - xhr.open('POST', _.str.sprintf('%s/%s', buildPath, segment.operation)); + xhr.open('POST', '/runbot/build/' + data.runbotBuild + '/' + operation); xhr.send(); }); }); - //$(function() { - // new Clipboard('.clipbtn'); - //}); })(jQuery); + function copyToClipboard(text) { - if (!navigator.clipboard) { - console.error('Clipboard not supported'); - return; - } - navigator.clipboard.writeText(text); + if (!navigator.clipboard) { + console.error('Clipboard not supported'); + return; } + navigator.clipboard.writeText(text); +} diff --git a/runbot/templates/assets.xml b/runbot/templates/assets.xml deleted file mode 100644 index 3aee9a2d..00000000 --- a/runbot/templates/assets.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/runbot/templates/batch.xml b/runbot/templates/batch.xml index f35fe5a5..e8ade5d9 100644 --- a/runbot/templates/batch.xml +++ b/runbot/templates/batch.xml @@ -2,7 +2,7 @@