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 @@