diff --git a/runbot/__manifest__.py b/runbot/__manifest__.py
index 1583afd0..f29a7c28 100644
--- a/runbot/__manifest__.py
+++ b/runbot/__manifest__.py
@@ -6,7 +6,7 @@
     'author': "Odoo SA",
     'website': "http://runbot.odoo.com",
     'category': 'Website',
-    'version': '4.6',
+    'version': '4.8',
     'depends': ['website', 'base'],
     'data': [
         'security/runbot_security.xml',
@@ -33,5 +33,6 @@
         'data/build_parse.xml',
         'data/runbot_error_regex_data.xml',
         'data/error_link.xml',
+        'data/website_data.xml',
     ],
 }
diff --git a/runbot/common.py b/runbot/common.py
index 6c5e6812..4ad24b10 100644
--- a/runbot/common.py
+++ b/runbot/common.py
@@ -54,7 +54,7 @@ def time2str(t):
 
 def dt2time(datetime):
     """Convert datetime to time"""
-    return time.mktime(time.strptime(datetime, DEFAULT_SERVER_DATETIME_FORMAT))
+    return time.mktime(datetime.timetuple())
 
 
 def now():
diff --git a/runbot/controllers/badge.py b/runbot/controllers/badge.py
index 5903ac34..e6890e78 100644
--- a/runbot/controllers/badge.py
+++ b/runbot/controllers/badge.py
@@ -35,7 +35,7 @@ class RunbotBadge(Controller):
 
         build = builds[0]
         etag = request.httprequest.headers.get('If-None-Match')
-        retag = hashlib.md5(build[last_update].encode()).hexdigest()
+        retag = hashlib.md5(str(build[last_update]).encode()).hexdigest()
 
         if etag == retag:
             return werkzeug.wrappers.Response(status=304)
diff --git a/runbot/controllers/frontend.py b/runbot/controllers/frontend.py
index 4ad29ddf..12b6442c 100644
--- a/runbot/controllers/frontend.py
+++ b/runbot/controllers/frontend.py
@@ -39,6 +39,7 @@ class Runbot(Controller):
             'host_stats': [],
             'pending_total': pending[0],
             'pending_level': pending[1],
+            'hosts_data': request.env['runbot.host'].search([]),
             'search': search,
             'refresh': refresh,
         }
@@ -101,15 +102,11 @@ class Runbot(Controller):
             def branch_info(branch):
                 return {
                     'branch': branch,
-                    'fqdn': fqdn(),
                     'builds': [build_dict[build_id] for build_id in build_by_branch_ids.get(branch.id) or []]
                 }
 
             context.update({
                 'branches': [branch_info(b) for b in branches],
-                'testing': build_obj.search_count([('repo_id', '=', repo.id), ('local_state', '=', 'testing')]),
-                'running': build_obj.search_count([('repo_id', '=', repo.id), ('local_state', '=', 'running')]),
-                'pending': build_obj.search_count([('repo_id', '=', repo.id), ('local_state', '=', 'pending')]),
                 'qu': QueryURL('/runbot/repo/' + slug(repo), search=search, refresh=refresh),
                 'fqdn': fqdn(),
             })
@@ -117,14 +114,6 @@ class Runbot(Controller):
         # consider host gone if no build in last 100
         build_threshold = max(build_ids or [0]) - 100
 
-        for result in build_obj.read_group([('id', '>', build_threshold)], ['host'], ['host']):
-            if result['host']:
-                context['host_stats'].append({
-                    'host': result['host'],
-                    'testing': build_obj.search_count([('local_state', '=', 'testing'), ('host', '=', result['host'])]),
-                    'running': build_obj.search_count([('local_state', '=', 'running'), ('host', '=', result['host'])]),
-                })
-
         context.update({'message': request.env['ir.config_parameter'].sudo().get_param('runbot.runbot_message')})
         return request.render('runbot.repo', context)
 
@@ -302,21 +291,27 @@ class Runbot(Controller):
         return request.render("runbot.glances", qctx)
 
     @route('/runbot/monitoring', type='http', auth='user', website=True)
-    def monitoring(self, refresh=None):
+    @route('/runbot/monitoring/<int:config_id>', type='http', auth='user', website=True)
+    @route('/runbot/monitoring/<int:config_id>/<int:view_id>', type='http', auth='user', website=True)
+    def monitoring(self, config_id=None, view_id=None, refresh=None):
         glances_ctx = self._glances_ctx()
         pending = self._pending()
         hosts_data = request.env['runbot.host'].search([])
 
-        monitored_config_id = int(request.env['ir.config_parameter'].sudo().get_param('runbot.monitored_config_id', 1))
-        request.env.cr.execute("""SELECT DISTINCT ON (branch_id) branch_id, id FROM runbot_build
-                                WHERE config_id = %s
-                                AND global_state in ('running', 'done')
-                                AND branch_id in (SELECT id FROM runbot_branch where sticky='t')
-                                AND local_state != 'duplicate'
-                                ORDER BY branch_id ASC, id DESC""", [int(monitored_config_id)])
-        last_monitored = request.env['runbot.build'].browse([r[1] for r in request.env.cr.fetchall()])
+        last_monitored = None
+        if config_id or config_id is None:
+            monitored_config_id = config_id or int(request.env['ir.config_parameter'].sudo().get_param('runbot.monitored_config_id', 1))
+            request.env.cr.execute("""SELECT DISTINCT ON (branch_id) branch_id, id FROM runbot_build
+                                    WHERE config_id = %s
+                                    AND global_state in ('running', 'done')
+                                    AND branch_id in (SELECT id FROM runbot_branch where sticky='t')
+                                    AND local_state != 'duplicate'
+                                    ORDER BY branch_id ASC, id DESC""", [int(monitored_config_id)])
+            last_monitored = request.env['runbot.build'].browse([r[1] for r in request.env.cr.fetchall()])
 
+        config = request.env['runbot.build.config'].browse(monitored_config_id)
         qctx = {
+            'config': config,
             'refresh': refresh,
             'pending_total': pending[0],
             'pending_level': pending[1],
@@ -326,7 +321,7 @@ class Runbot(Controller):
             'auto_tags': request.env['runbot.build.error'].disabling_tags(),
             'build_errors': request.env['runbot.build.error'].search([('random', '=', True)])
         }
-        return request.render("runbot.monitoring", qctx)
+        return request.render(request.env['ir.ui.view'].browse('view_id') if view_id else config.monitoring_view_id.id or "runbot.monitoring", qctx)
 
     @route(['/runbot/branch/<int:branch_id>', '/runbot/branch/<int:branch_id>/page/<int:page>'], website=True, auth='public', type='http')
     def branch_builds(self, branch_id=None, search='', page=1, limit=50, refresh='', **kwargs):
@@ -337,9 +332,9 @@ class Runbot(Controller):
             url='/runbot/branch/%s' % branch_id,
             total=builds_count,
             page=page,
-            step=50
+            step=50,
         )
         builds = request.env['runbot.build'].search(domain, limit=limit, offset=pager.get('offset',0))
 
-        context = {'pager': pager, 'builds': builds}
+        context = {'pager': pager, 'builds': builds, 'repo': request.env['runbot.branch'].browse(branch_id).repo_id}
         return request.render("runbot.branch", context)
diff --git a/runbot/controllers/hook.py b/runbot/controllers/hook.py
index b6eedd27..aeb70bb4 100644
--- a/runbot/controllers/hook.py
+++ b/runbot/controllers/hook.py
@@ -31,7 +31,7 @@ class RunbotHook(http.Controller):
 
         # force update of dependencies to in case a hook is lost
         if not payload or event == 'push' or (event == 'pull_request' and payload.get('action') in ('synchronize', 'opened', 'reopened')):
-            (repo | repo.dependency_ids).write({'hook_time': time.time()})
+            (repo | repo.dependency_ids).set_hook_time(time.time())
         else:
             _logger.debug('Ignoring unsupported hook %s %s', event, payload.get('action', ''))
         return ""
diff --git a/runbot/data/website_data.xml b/runbot/data/website_data.xml
new file mode 100644
index 00000000..104c9acf
--- /dev/null
+++ b/runbot/data/website_data.xml
@@ -0,0 +1,5 @@
+<odoo>
+    <record id="website.homepage_page" model="website.page">
+        <field name="url">/home</field>
+    </record>
+</odoo>
diff --git a/runbot/migrations/1.3/post-logging-build_id.py b/runbot/migrations/11.0.1.3/post-logging-build_id.py
similarity index 100%
rename from runbot/migrations/1.3/post-logging-build_id.py
rename to runbot/migrations/11.0.1.3/post-logging-build_id.py
diff --git a/runbot/migrations/4.0/post-migration.py b/runbot/migrations/11.0.4.0/post-migration.py
similarity index 100%
rename from runbot/migrations/4.0/post-migration.py
rename to runbot/migrations/11.0.4.0/post-migration.py
diff --git a/runbot/migrations/4.0/pre-migration.py b/runbot/migrations/11.0.4.0/pre-migration.py
similarity index 100%
rename from runbot/migrations/4.0/pre-migration.py
rename to runbot/migrations/11.0.4.0/pre-migration.py
diff --git a/runbot/migrations/4.4/post-migration.py b/runbot/migrations/11.0.4.4/post-migration.py
similarity index 100%
rename from runbot/migrations/4.4/post-migration.py
rename to runbot/migrations/11.0.4.4/post-migration.py
diff --git a/runbot/migrations/4.5/post-migration.py b/runbot/migrations/11.0.4.5/post-migration.py
similarity index 100%
rename from runbot/migrations/4.5/post-migration.py
rename to runbot/migrations/11.0.4.5/post-migration.py
diff --git a/runbot/models/branch.py b/runbot/models/branch.py
index 16aa14a8..b7129a99 100644
--- a/runbot/models/branch.py
+++ b/runbot/models/branch.py
@@ -12,6 +12,7 @@ _re_patch = re.compile(r'.*patch-\d+$')
 class runbot_branch(models.Model):
 
     _name = "runbot.branch"
+    _description = "Branch"
     _order = 'name'
     _sql_constraints = [('branch_repo_uniq', 'unique (name,repo_id)', 'The branch must be unique per repository !')]
 
@@ -35,7 +36,7 @@ class runbot_branch(models.Model):
     no_auto_build = fields.Boolean("Don't automatically build commit on this branch", default=False)
     rebuild_requested = fields.Boolean("Request a rebuild", help="Rebuild the latest commit even when no_auto_build is set.", default=False)
 
-    branch_config_id = fields.Many2one('runbot.build.config', 'Run Config')
+    branch_config_id = fields.Many2one('runbot.build.config', 'Branch Config')
     config_id = fields.Many2one('runbot.build.config', 'Run Config', compute='_compute_config_id', inverse='_inverse_config_id')
 
     @api.depends('sticky', 'defined_sticky', 'target_branch_name', 'name')
@@ -47,16 +48,24 @@ class runbot_branch(models.Model):
             elif branch.defined_sticky:
                 branch.closest_sticky = branch.defined_sticky # be carefull with loop
             elif branch.target_branch_name:
-                corresping_branch = self.search([('branch_name', '=', branch.target_branch_name), ('repo_id', '=', branch.repo_id.id)])
-                branch.closest_sticky = corresping_branch.closest_sticky
+                corresponding_branch = self.search([('branch_name', '=', branch.target_branch_name), ('repo_id', '=', branch.repo_id.id)])
+                branch.closest_sticky = corresponding_branch.closest_sticky
             else:
                 repo_ids = (branch.repo_id | branch.repo_id.duplicate_id).ids
                 self.env.cr.execute("select id from runbot_branch where sticky = 't' and repo_id = any(%s) and %s like name||'%%'", (repo_ids, branch.name or ''))
                 branch.closest_sticky = self.browse(self.env.cr.fetchone())
 
-    @api.depends('closest_sticky.previous_version')
+    @api.depends('closest_sticky') #, 'closest_sticky.previous_version')
     def _compute_previous_version(self):
-        for branch in self:
+        for branch in self.sorted(key='sticky', reverse=True):
+            # orm does not support non_searchable.non_stored dependency.
+            # thus, the closest_sticky.previous_version dependency will log an error
+            # when previous_version is written.
+            # this dependency is usefull to make the compute recursive, avoiding to have 
+            # both record and record.closest_sticky in self, in that order, making the record.previous_version
+            # empty in all cases.
+            # Sorting self on sticky will mitigate the problem. but it is still posible to
+            # have computation errors if defined_sticky is not sticky. (which is not a normal use case)
             if branch.closest_sticky == branch:
                 repo_ids = (branch.repo_id | branch.repo_id.duplicate_id).ids
                 domain = [('branch_name', 'like', '%.0'), ('sticky', '=', True), ('branch_name', '!=', 'master'), ('repo_id', 'in', repo_ids)]
@@ -66,11 +75,12 @@ class runbot_branch(models.Model):
             else:
                 branch.previous_version = branch.closest_sticky.previous_version
 
-    @api.depends('previous_version', 'closest_sticky.intermediate_stickies')
+    @api.depends('previous_version', 'closest_sticky')
     def _compute_intermediate_stickies(self):
-        for branch in self:
+        for branch in self.sorted(key='sticky', reverse=True):
             if branch.closest_sticky == branch:
                 if not branch.previous_version:
+                    branch.intermediate_stickies = [(5, 0, 0)]
                     continue
                 repo_ids = (branch.repo_id | branch.repo_id.duplicate_id).ids
                 domain = [('id', '>', branch.previous_version.id), ('sticky', '=', True), ('branch_name', '!=', 'master'), ('repo_id', 'in', repo_ids)]
@@ -133,7 +143,7 @@ class runbot_branch(models.Model):
             return False
         return True
 
-    @api.model
+    @api.model_create_single
     def create(self, vals):
         if not vals.get('config_id') and ('use-coverage' in (vals.get('name') or '')):
             coverage_config = self.env.ref('runbot.runbot_build_config_test_coverage', raise_if_not_found=False)
@@ -287,6 +297,6 @@ class runbot_branch(models.Model):
         for branch in self:
             if not branch.rebuild_requested:
                 branch.rebuild_requested = True
-                branch.repo_id.write({'hook_time': time.time()})
+                branch.repo_id.set_hook_time(time.time())
             else:
                 branch.rebuild_requested = False
diff --git a/runbot/models/build.py b/runbot/models/build.py
index 3c08a4b4..5d8fdeb3 100644
--- a/runbot/models/build.py
+++ b/runbot/models/build.py
@@ -32,6 +32,8 @@ def make_selection(array):
 
 class runbot_build(models.Model):
     _name = "runbot.build"
+    _description = "Build"
+
     _order = 'id desc'
     _rec_name = 'id'
 
@@ -55,16 +57,16 @@ class runbot_build(models.Model):
     # state machine
 
     global_state = fields.Selection(make_selection(state_order), string='Status', compute='_compute_global_state', store=True)
-    local_state = fields.Selection(make_selection(state_order), string='Build Status', default='pending', required=True, oldname='state', index=True)
+    local_state = fields.Selection(make_selection(state_order), string='Build Status', default='pending', required=True, index=True)
     global_result = fields.Selection(make_selection(result_order), string='Result', compute='_compute_global_result', store=True)
-    local_result = fields.Selection(make_selection(result_order), string='Build Result', oldname='result')
+    local_result = fields.Selection(make_selection(result_order), string='Build Result')
     triggered_result = fields.Selection(make_selection(result_order), string='Triggered Result')  # triggered by db only
 
     requested_action = fields.Selection([('wake_up', 'To wake up'), ('deathrow', 'To kill')], string='Action requested', index=True)
 
     nb_pending = fields.Integer("Number of pending in queue", default=0)
     nb_testing = fields.Integer("Number of test slot use", default=0)
-    nb_running = fields.Integer("Number of test slot use", default=0)
+    nb_running = fields.Integer("Number of run slot use", default=0)
 
     # should we add a stored field for children results?
     active_step = fields.Many2one('runbot.build.config.step', 'Active step')
@@ -74,7 +76,7 @@ class runbot_build(models.Model):
     build_start = fields.Datetime('Build start')
     build_end = fields.Datetime('Build end')
     job_time = fields.Integer(compute='_compute_job_time', string='Job time')
-    build_time = fields.Integer(compute='_compute_build_time', string='Job time')
+    build_time = fields.Integer(compute='_compute_build_time', string='Build time')
     build_age = fields.Integer(compute='_compute_build_age', string='Build age')
     duplicate_id = fields.Many2one('runbot.build', 'Corresponding Build', index=True)
     revdep_build_ids = fields.Many2many('runbot.build', 'runbot_rev_dep_builds',
@@ -116,18 +118,21 @@ class runbot_build(models.Model):
         for build in self:
             build.log_list = ','.join({step.name for step in build.config_id.step_ids() if step._has_log()})
 
-    @api.depends('nb_testing', 'nb_pending', 'local_state', 'duplicate_id.global_state')
+    @api.depends('children_ids.global_state', 'local_state', 'duplicate_id.global_state')
     def _compute_global_state(self):
-        # could we use nb_pending / nb_testing ? not in a compute, but in a update state method
         for record in self:
             if record.duplicate_id:
                 record.global_state = record.duplicate_id.global_state
             else:
                 waiting_score = record._get_state_score('waiting')
-                if record._get_state_score(record.local_state) < waiting_score or record.nb_pending + record.nb_testing == 0:
-                    record.global_state = record.local_state
+                if record._get_state_score(record.local_state) > waiting_score and record.children_ids:  # if finish, check children
+                    children_state = record._get_youngest_state([child.global_state for child in record.children_ids])
+                    if record._get_state_score(children_state) > waiting_score:
+                        record.global_state = record.local_state
+                    else:
+                        record.global_state = 'waiting'
                 else:
-                    record.global_state = 'waiting'
+                    record.global_state = record.local_state
 
     def _get_youngest_state(self, states):
         index = min([self._get_state_score(state) for state in states])
@@ -166,24 +171,7 @@ class runbot_build(models.Model):
     def _get_result_score(self, result):
         return result_order.index(result)
 
-    def _update_nb_children(self, new_state, old_state=None):
-        # could be interresting to update state in batches.
-        tracked_count_list = ['pending', 'testing', 'running']
-        if (new_state not in tracked_count_list and old_state not in tracked_count_list) or new_state == old_state:
-            return
-
-        for record in self:
-            values = {}
-            if old_state in tracked_count_list:
-                values['nb_%s' % old_state] = record['nb_%s' % old_state] - 1
-            if new_state in tracked_count_list:
-                values['nb_%s' % new_state] = record['nb_%s' % new_state] + 1
-
-            record.write(values)
-            if record.parent_id:
-                record.parent_id._update_nb_children(new_state, old_state)
-
-    @api.depends('real_build.active_step')
+    @api.depends('active_step', 'duplicate_id.active_step')
     def _compute_job(self):
         for build in self:
             build.job = build.real_build.active_step.name
@@ -196,13 +184,13 @@ class runbot_build(models.Model):
     def copy(self, values=None):
         raise UserError("Cannot duplicate build!")
 
+    @api.model_create_single
     def create(self, vals):
         branch = self.env['runbot.branch'].search([('id', '=', vals.get('branch_id', False))])  # branche 10174?
         if branch.no_build:
             return self.env['runbot.build']
         vals['config_id'] = vals['config_id'] if 'config_id' in vals else branch.config_id.id
         build_id = super(runbot_build, self).create(vals)
-        build_id._update_nb_children(build_id.local_state)
         extra_info = {'sequence': build_id.id if not build_id.sequence else build_id.sequence}
         context = self.env.context
 
@@ -309,8 +297,6 @@ class runbot_build(models.Model):
             build_by_old_values = defaultdict(lambda: self.env['runbot.build'])
             for record in self:
                 build_by_old_values[record.local_state] += record
-            for local_state, builds in build_by_old_values.items():
-                builds._update_nb_children(values.get('local_state'), local_state)
         assert 'state' not in values
         local_result = values.get('local_result')
         for build in self:
@@ -318,6 +304,8 @@ class runbot_build(models.Model):
         res = super(runbot_build, self).write(values)
         for build in self:
             assert bool(not build.duplicate_id) ^ (build.local_state == 'duplicate')  # don't change duplicate state without removing duplicate id.
+        if 'log_counter' in values: # not 100% usefull but more correct ( see test_ir_logging)
+            self.flush()
         return res
 
     def update_build_end(self):
@@ -360,6 +348,8 @@ class runbot_build(models.Model):
                 build.job_time = int(dt2time(build.job_end) - dt2time(build.job_start))
             elif build.job_start:
                 build.job_time = int(time.time() - dt2time(build.job_start))
+            else:
+                build.job_time = 0
 
     @api.depends('build_start', 'build_end', 'duplicate_id.build_time')
     def _compute_build_time(self):
@@ -370,6 +360,8 @@ class runbot_build(models.Model):
                 build.build_time = int(dt2time(build.build_end) - dt2time(build.build_start))
             elif build.build_start:
                 build.build_time = int(time.time() - dt2time(build.build_start))
+            else:
+                build.build_time = 0
 
     @api.depends('job_start', 'duplicate_id.build_age')
     def _compute_build_age(self):
@@ -379,6 +371,8 @@ class runbot_build(models.Model):
                 build.build_age = build.duplicate_id.build_age
             elif build.job_start:
                 build.build_age = int(time.time() - dt2time(build.build_start))
+            else:
+                build.build_age = 0
 
     def _get_params(self):
         try:
diff --git a/runbot/models/build_config.py b/runbot/models/build_config.py
index 29e2fbde..8518844c 100644
--- a/runbot/models/build_config.py
+++ b/runbot/models/build_config.py
@@ -21,6 +21,7 @@ PYTHON_DEFAULT = "# type python code here\n\n\n\n\n\n"
 
 class Config(models.Model):
     _name = "runbot.build.config"
+    _description = "Build config"
     _inherit = "mail.thread"
 
     name = fields.Char('Config name', required=True, unique=True, track_visibility='onchange', help="Unique name for config please use trigram as postfix for custom configs")
@@ -29,9 +30,10 @@ class Config(models.Model):
     update_github_state = fields.Boolean('Notify build state to github', default=False, track_visibility='onchange')
     protected = fields.Boolean('Protected', default=False, track_visibility='onchange')
     group = fields.Many2one('runbot.build.config', 'Configuration group', help="Group of config's and config steps")
-    group_name = fields.Char(related='group.name')
+    group_name = fields.Char('Group name', related='group.name')
+    monitoring_view_id = fields.Many2one('ir.ui.view', 'Monitoring view')
 
-    @api.model
+    @api.model_create_single
     def create(self, values):
         res = super(Config, self).create(values)
         res._check_step_ids_order()
@@ -84,6 +86,7 @@ class Config(models.Model):
 
 class ConfigStep(models.Model):
     _name = 'runbot.build.config.step'
+    _description = "Config step"
     _inherit = 'mail.thread'
 
     # general info
@@ -161,7 +164,7 @@ class ConfigStep(models.Model):
         copy._write({'protected': False})
         return copy
 
-    @api.model
+    @api.model_create_single
     def create(self, values):
         self._check(values)
         return super(ConfigStep, self).create(values)
@@ -560,6 +563,7 @@ class ConfigStep(models.Model):
 
 class ConfigStepOrder(models.Model):
     _name = 'runbot.build.config.step.order'
+    _description = "Config step order"
     _order = 'sequence, id'
     # a kind of many2many rel with sequence
 
@@ -571,7 +575,7 @@ class ConfigStepOrder(models.Model):
     def _onchange_step_id(self):
         self.sequence = self.step_id.default_sequence
 
-    @api.model
+    @api.model_create_single
     def create(self, values):
         if 'sequence' not in values and values.get('step_id'):
             values['sequence'] = self.env['runbot.build.config.step'].browse(values.get('step_id')).default_sequence
diff --git a/runbot/models/build_dependency.py b/runbot/models/build_dependency.py
index dc246341..0c8090dd 100644
--- a/runbot/models/build_dependency.py
+++ b/runbot/models/build_dependency.py
@@ -3,6 +3,7 @@ from odoo import models, fields
 
 class RunbotBuildDependency(models.Model):
     _name = "runbot.build.dependency"
+    _description = "Build dependency"
 
     build_id = fields.Many2one('runbot.build', 'Build', required=True, ondelete='cascade', index=True)
     dependecy_repo_id = fields.Many2one('runbot.repo', 'Dependency repo', required=True, ondelete='cascade')
diff --git a/runbot/models/build_error.py b/runbot/models/build_error.py
index 723d2052..57b8b28a 100644
--- a/runbot/models/build_error.py
+++ b/runbot/models/build_error.py
@@ -13,6 +13,8 @@ _logger = logging.getLogger(__name__)
 class RunbotBuildError(models.Model):
 
     _name = "runbot.build.error"
+    _description = "Build error"
+
     _inherit = "mail.thread"
     _rec_name = "id"
 
@@ -34,7 +36,7 @@ class RunbotBuildError(models.Model):
     parent_id = fields.Many2one('runbot.build.error', 'Linked to')
     child_ids = fields.One2many('runbot.build.error', 'parent_id', string='Child Errors', context={'active_test': False})
     children_build_ids = fields.Many2many('runbot.build', compute='_compute_children_build_ids', string='Children builds')
-    error_history_ids = fields.One2many('runbot.build.error', compute='_compute_error_history_ids', string='Old errors')
+    error_history_ids = fields.Many2many('runbot.build.error', compute='_compute_error_history_ids', string='Old errors', context={'active_test': False})
     first_seen_build_id = fields.Many2one('runbot.build', compute='_compute_first_seen_build_id', string='First Seen build')
     first_seen_date = fields.Datetime(string='First Seen Date', related='first_seen_build_id.create_date')
     last_seen_build_id = fields.Many2one('runbot.build', compute='_compute_last_seen_build_id', string='Last Seen build')
@@ -47,7 +49,7 @@ class RunbotBuildError(models.Model):
             if build_error.test_tags and '-' in build_error.test_tags:
                 raise ValidationError('Build error test_tags should not be negated')
 
-    @api.model
+    @api.model_create_single
     def create(self, vals):
         cleaners = self.env['runbot.error.regex'].search([('re_type', '=', 'cleaning')])
         content = vals.get('content')
@@ -57,7 +59,6 @@ class RunbotBuildError(models.Model):
         })
         return super().create(vals)
 
-    @api.multi
     def write(self, vals):
         if 'active' in vals:
             for build_error in self:
@@ -100,7 +101,7 @@ class RunbotBuildError(models.Model):
         for build_error in self:
             build_error.first_seen_build_id = build_error.children_build_ids and build_error.children_build_ids[-1] or False
 
-    @api.depends('fingerprint')
+    @api.depends('fingerprint', 'child_ids.fingerprint')
     def _compute_error_history_ids(self):
         for error in self:
             fingerprints = [error.fingerprint] + [rec.fingerprint for rec in error.child_ids]
@@ -160,9 +161,9 @@ class RunbotBuildError(models.Model):
 
     @api.model
     def test_tags_list(self):
-        active_errors = self.search([('test_tags', '!=', 'False'), ('random', '=', True)])
+        active_errors = self.search([('test_tags', '!=', False), ('random', '=', True)])
         test_tag_list = active_errors.mapped('test_tags')
-        return [test_tag for error_tags in test_tag_list for test_tag in error_tags.split(',')]
+        return [test_tag for error_tags in test_tag_list for test_tag in (error_tags).split(',')]
 
     @api.model
     def disabling_tags(self):
@@ -172,6 +173,7 @@ class RunbotBuildError(models.Model):
 class RunbotBuildErrorTag(models.Model):
 
     _name = "runbot.build.error.tag"
+    _description = "Build error tag"
 
     name = fields.Char('Tag')
     error_ids = fields.Many2many('runbot.build.error', string='Errors')
@@ -180,6 +182,7 @@ class RunbotBuildErrorTag(models.Model):
 class RunbotErrorRegex(models.Model):
 
     _name = "runbot.error.regex"
+    _description = "Build error regex"
     _inherit = "mail.thread"
     _rec_name = 'id'
     _order = 'sequence, id'
diff --git a/runbot/models/event.py b/runbot/models/event.py
index 5a888a7e..e4f7fff9 100644
--- a/runbot/models/event.py
+++ b/runbot/models/event.py
@@ -16,9 +16,8 @@ class runbot_event(models.Model):
 
     build_id = fields.Many2one('runbot.build', 'Build', index=True, ondelete='cascade')
     active_step_id = fields.Many2one('runbot.build.config.step', 'Active step', index=True)
-    type = fields.Selection(TYPES, string='Type', required=True, index=True)
+    type = fields.Selection(selection_add=TYPES, string='Type', required=True, index=True)
 
-    @api.model_cr
     def init(self):
         parent_class = super(runbot_event, self)
         if hasattr(parent_class, 'init'):
@@ -75,6 +74,7 @@ FOR EACH ROW EXECUTE PROCEDURE runbot_set_logging_build();
 
 class RunbotErrorLog(models.Model):
     _name = "runbot.error.log"
+    _description = "Error log"
     _auto = False
     _order = 'id desc'
 
@@ -130,7 +130,6 @@ class RunbotErrorLog(models.Model):
         BuildError._parse_logs(self)
 
 
-    @api.model_cr
     def init(self):
         """ Create an SQL view for ir.logging """
         tools.drop_view_if_exists(self._cr, 'runbot_error_log')
diff --git a/runbot/models/host.py b/runbot/models/host.py
index b5bdb031..6ac52778 100644
--- a/runbot/models/host.py
+++ b/runbot/models/host.py
@@ -6,6 +6,7 @@ _logger = logging.getLogger(__name__)
 
 class RunboHost(models.Model):
     _name = "runbot.host"
+    _description = "Host"
     _order = 'id'
     _inherit = 'mail.thread'
 
@@ -37,7 +38,7 @@ class RunboHost(models.Model):
             host.nb_testing = count_by_host_state[host.name].get('testing', 0)
             host.nb_running = count_by_host_state[host.name].get('running', 0)
 
-    @api.model
+    @api.model_create_single
     def create(self, values):
         if not 'disp_name' in values:
             values['disp_name'] = values['name']
@@ -57,10 +58,15 @@ class RunboHost(models.Model):
         return int(icp.get_param('runbot.runbot_running_max', default=75))
 
     def set_psql_conn_count(self):
-
         _logger.debug('Updating psql connection count...')
         self.ensure_one()
         with local_pgadmin_cursor() as local_cr:
             local_cr.execute("SELECT sum(numbackends) FROM pg_stat_database;")
             res = local_cr.fetchone()
         self.psql_conn_count = res and res[0] or 0
+
+    def _total_testing(self):
+        return sum(host.nb_testing for host in self)
+
+    def _total_workers(self):
+        return sum(host.get_nb_worker() for host in self)
diff --git a/runbot/models/ir_cron.py b/runbot/models/ir_cron.py
index a1fbd529..527afa26 100644
--- a/runbot/models/ir_cron.py
+++ b/runbot/models/ir_cron.py
@@ -4,7 +4,7 @@ from dateutil.relativedelta import relativedelta
 from odoo import models, fields
 
 odoo.service.server.SLEEP_INTERVAL = 5
-odoo.addons.base.ir.ir_cron._intervalTypes['seconds'] = lambda interval: relativedelta(seconds=interval)
+odoo.addons.base.models.ir_cron._intervalTypes['seconds'] = lambda interval: relativedelta(seconds=interval)
 
 class ir_cron(models.Model):
     _inherit = "ir.cron"
diff --git a/runbot/models/repo.py b/runbot/models/repo.py
index d74c92a9..36af0b3f 100644
--- a/runbot/models/repo.py
+++ b/runbot/models/repo.py
@@ -30,10 +30,11 @@ class RunbotException(Exception):
 class runbot_repo(models.Model):
 
     _name = "runbot.repo"
+    _description = "Repo"
     _order = 'sequence, id'
 
     name = fields.Char('Repository', required=True)
-    short_name = fields.Char('Repository', compute='_compute_short_name', store=False, readonly=True)
+    short_name = fields.Char('Short name', compute='_compute_short_name', store=False, readonly=True)
     sequence = fields.Integer('Sequence')
     path = fields.Char(compute='_get_path', string='Directory', readonly=True)
     base = fields.Char(compute='_get_base_url', string='Base URL', readonly=True)  # Could be renamed to a more explicit name like base_url
@@ -60,7 +61,7 @@ class runbot_repo(models.Model):
     token = fields.Char("Github token", groups="runbot.group_runbot_admin")
     group_ids = fields.Many2many('res.groups', string='Limited to groups')
 
-    repo_config_id = fields.Many2one('runbot.build.config', 'Run Config')
+    repo_config_id = fields.Many2one('runbot.build.config', 'Repo Config')
     config_id = fields.Many2one('runbot.build.config', 'Run Config', compute='_compute_config_id', inverse='_inverse_config_id')
 
     server_files = fields.Char('Server files', help='Comma separated list of possible server files')  # odoo-bin,openerp-server,openerp-server.py
@@ -104,20 +105,15 @@ class runbot_repo(models.Model):
         for repo in self:
             repo.hook_time = times.get(repo.id, 0)
 
-    def write(self, values):
-        # hooktime and reftime table are here to avoid sql update on repo.
-        # using inverse will still trigger write_date and write_uid update.
-        # this hack allows to avoid that
-
-        hook_time = values.pop('hook_time', None)
-        get_ref_time = values.pop('get_ref_time', None)
+    def set_hook_time(self, value):
         for repo in self:
-            if hook_time:
-                self.env['runbot.repo.hooktime'].create({'time': hook_time, 'repo_id': repo.id})
-            if get_ref_time:
-                self.env['runbot.repo.reftime'].create({'time': get_ref_time, 'repo_id': repo.id})
-        if values:
-            super().write(values)
+            self.env['runbot.repo.hooktime'].create({'time': value, 'repo_id': repo.id})
+        self.invalidate_cache()
+
+    def set_ref_time(self, value):
+        for repo in self:
+            self.env['runbot.repo.reftime'].create({'time': value, 'repo_id': repo.id})
+        self.invalidate_cache()
 
     def _gc_times(self):
         self.env.cr.execute("""
@@ -283,7 +279,7 @@ class runbot_repo(models.Model):
 
         get_ref_time = round(self._get_fetch_head_time(), 4)
         if not self.get_ref_time or get_ref_time > self.get_ref_time:
-            self.get_ref_time = get_ref_time
+            self.set_ref_time(get_ref_time)
             fields = ['refname', 'objectname', 'committerdate:iso8601', 'authorname', 'authoremail', 'subject', 'committername', 'committeremail']
             fmt = "%00".join(["%(" + field + ")" for field in fields])
             git_refs = self._git(['for-each-ref', '--format', fmt, '--sort=-committerdate', 'refs/heads', 'refs/pull'])
@@ -392,7 +388,7 @@ class runbot_repo(models.Model):
                                 indirect.build_type = 'indirect'
                                 new_build.revdep_build_ids += indirect
 
-    @api.multi
+
     def _create_pending_builds(self):
         """ Find new commits in physical repos"""
         refs = {}
@@ -451,7 +447,6 @@ class runbot_repo(models.Model):
         repo = self
         repo._git(['fetch', '-p', 'origin', '+refs/heads/*:refs/heads/*', '+refs/pull/*/head:refs/pull/*'])
 
-    @api.multi
     def _update(self, force=True):
         """ Update the physical git reposotories on FS"""
         for repo in reversed(self):
@@ -462,10 +457,9 @@ class runbot_repo(models.Model):
 
     def _commit(self):
         self.env.cr.commit()
-        self.invalidate_cache()
-        self.env.reset()
+        self.env.cache.invalidate()
+        self.env.clear()
 
-    @api.multi
     def _scheduler(self, host):
         nb_workers = host.get_nb_worker()
 
@@ -693,7 +687,7 @@ class runbot_repo(models.Model):
             self._commit()
         except Exception as e:
             self.env.cr.rollback()
-            self.env.reset()
+            self.env.clear()
             _logger.exception(e)
             message = str(e)
             if host.last_exception == message:
@@ -765,6 +759,7 @@ class runbot_repo(models.Model):
 
 class RefTime(models.Model):
     _name = "runbot.repo.reftime"
+    _description = "Repo reftime"
     _log_access = False
 
     time = fields.Float('Time', index=True, required=True)
@@ -773,7 +768,8 @@ class RefTime(models.Model):
 
 class HookTime(models.Model):
     _name = "runbot.repo.hooktime"
+    _description = "Repo hooktime"
     _log_access = False
 
     time = fields.Float('Time')
-    repo_id = fields.Many2one('runbot.repo', 'Repository', required=True, ondelete='cascade')
\ No newline at end of file
+    repo_id = fields.Many2one('runbot.repo', 'Repository', required=True, ondelete='cascade')
diff --git a/runbot/models/res_config_settings.py b/runbot/models/res_config_settings.py
index 202b87e6..3afc2356 100644
--- a/runbot/models/res_config_settings.py
+++ b/runbot/models/res_config_settings.py
@@ -33,7 +33,6 @@ class ResConfigSettings(models.TransientModel):
                    )
         return res
 
-    @api.multi
     def set_values(self):
         super(ResConfigSettings, self).set_values()
         set_param = self.env['ir.config_parameter'].sudo().set_param
diff --git a/runbot/security/runbot_security.xml b/runbot/security/runbot_security.xml
index a4efeb10..9a77fdec 100644
--- a/runbot/security/runbot_security.xml
+++ b/runbot/security/runbot_security.xml
@@ -17,6 +17,10 @@
       <field name="implied_ids" eval="[(4, ref('runbot.group_user'))]"/>
     </record>
 
+    <record id="base.group_portal" model="res.groups">
+      <field name="implied_ids" eval="[(4, ref('runbot.group_user'))]"/>
+    </record>
+
     <record id="group_runbot_admin" model="res.groups">
       <field name="name">Manager</field>
       <field name="category_id" ref="module_category"/>
diff --git a/runbot/static/src/css/runbot.css b/runbot/static/src/css/runbot.css
new file mode 100644
index 00000000..97439884
--- /dev/null
+++ b/runbot/static/src/css/runbot.css
@@ -0,0 +1,80 @@
+.separator {
+    border-top: 2px solid #666;
+    font-weight: bold;
+}
+
+[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 }
+
+.branch_name {
+    max-width: 250px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.branch_time {
+    float:right;
+    margin-left:10px;
+}
+
+.bg-success-light {
+    background-color: #dff0d8;
+}
+.bg-danger-light {
+    background-color: #f2dede;
+}
+.bg-info-light {
+    background-color: #d9edf7;
+}
+
+.text-info{
+    color: #096b72 !important;
+}
+.build_subject_buttons {
+    display: flex;
+}
+.build_buttons {
+    margin-left: auto
+}
diff --git a/runbot/static/src/js/runbot.js b/runbot/static/src/js/runbot.js
index c8061be6..ca775d23 100644
--- a/runbot/static/src/js/runbot.js
+++ b/runbot/static/src/js/runbot.js
@@ -41,7 +41,7 @@
             return false;
        });
     });
-    $(function() {
-      new Clipboard('.clipbtn');
-    });
+    //$(function() {
+    //  new Clipboard('.clipbtn');
+    //});
 })(jQuery);
diff --git a/runbot/static/src/less/runbot.less b/runbot/static/src/less/runbot.less
deleted file mode 100644
index dbad1694..00000000
--- a/runbot/static/src/less/runbot.less
+++ /dev/null
@@ -1,12 +0,0 @@
-.separator {
-    border-top: 2px solid #666;
-    font-weight: bold;
-}
-
-[data-toggle="collapse"] .fa:before {
-    content: "\f139";
-}
-
-[data-toggle="collapse"].collapsed .fa:before {
-    content: "\f13a";
-}
diff --git a/runbot/templates/branch.xml b/runbot/templates/branch.xml
index 9e5f5f79..d92f8eb8 100644
--- a/runbot/templates/branch.xml
+++ b/runbot/templates/branch.xml
@@ -33,7 +33,7 @@
                                 <t t-if="build.global_result == 'skipped'"><t t-set="rowclass">default</t></t>
                                 <t t-if="build.global_result in ['killed', 'manually_killed']"><t t-set="rowclass">killed</t></t>
                             </t>
-                            <tr t-attf-class="{{rowclass}}">
+                            <tr t-attf-class="bg-{{rowclass}}-light">
                                 <td><t t-esc="build.create_date" /></td>
                                 <td><a t-attf-href="/runbot/build/{{build['id']}}" title="Build details" aria-label="Build details"><t t-esc="build.dest" /></a></td>
                                 <td>
diff --git a/runbot/templates/build.xml b/runbot/templates/build.xml
index 1aa8b790..e7a8a2f8 100644
--- a/runbot/templates/build.xml
+++ b/runbot/templates/build.xml
@@ -40,13 +40,6 @@
                 </t>
             </small>
         </t>
-        <t t-set="nb_sum" t-value="bu.nb_pending+bu.nb_testing+bu.nb_running"/>
-        <t t-if="nb_sum > 1"><!-- maybe only display this info if > 3 -->
-          <span t-attf-title="{{bu.nb_pending}} pending, {{bu.nb_testing}} testing, {{bu.nb_running}} running">
-            <t t-esc="nb_sum"/>
-            <i class="fa fa-cogs"/>
-          </span>
-        </t>
     </template>
     <template id="runbot.build_button">
         <div t-attf-class="pull-right">
@@ -54,9 +47,9 @@
                 <a t-if="bu.real_build.local_state=='running'" t-attf-href="http://{{bu['domain']}}/?db={{bu.real_build.dest}}-all" class="btn btn-primary" title="Sign in on this build" aria-label="Sign in on this build"><i class="fa fa-sign-in"/></a>
                 <a t-if="bu.real_build.local_state=='done' and bu.real_build.requested_action != 'wake_up'" href="#" t-att-data-runbot-build="bu.real_build.id" class="btn btn-default runbot-wakeup" title="Wake up this build" aria-label="Wake up this build"><i class="fa fa-coffee"/></a>
                 <a t-attf-href="/runbot/build/{{bu['id']}}" class="btn btn-default" title="Build details" aria-label="Build details"><i class="fa fa-file-text-o"/></a>
-                <a t-attf-href="https://#{repo.base}/commit/#{bu['name']}" class="btn btn-default" title="Open commit on GitHub" aria-label="Open commit on GitHub"><i class="fa fa-github"/></a>
+                <a t-if="show_commit_button" t-attf-href="https://#{repo.base}/commit/#{bu['name']}" class="btn btn-default" title="Open commit on GitHub" aria-label="Open commit on GitHub"><i class="fa fa-github"/></a>
                 <button class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="Build options" aria-label="Build options" aria-expanded="false"><i class="fa fa-cog"/><span class="caret"></span></button>
-                <ul class="dropdown-menu" role="menu">
+                <ul class="dropdown-menu dropdown-menu-right" role="menu">
                     <li t-if="bu.global_result=='skipped'" groups="runbot.group_runbot_admin">
                         <a href="#" class="runbot-rebuild" t-att-data-runbot-build="bu['id']">Force Build <i class="fa fa-level-up"></i></a>
                     </li>
@@ -89,12 +82,12 @@
                     <li t-if="bu.global_state not in ('testing', 'waiting', 'pending')" class="divider"></li>
                     <li><a t-attf-href="/runbot/build/{{bu['id']}}">Logs <i class="fa fa-file-text-o"/></a></li>
                     <t t-set="log_url" t-value="'http://%s' % bu.real_build.host if bu.real_build.host != fqdn else ''"/>
-                    <t t-if="bu.real_build.host" t-foreach="bu.log_list.split(',')" t-as="log_name" >
+                    <t t-if="bu.real_build.host" t-foreach="(bu.log_list or '').split(',')" t-as="log_name" >
                       <li><a t-attf-href="{{log_url}}/runbot/static/build/#{bu['real_build'].dest}/logs/#{log_name}.txt">Full <t t-esc="log_name"/> logs <i class="fa fa-file-text-o"/></a></li>
                     </t>
                     <li t-if="bu.coverage and bu.real_build.host"><a t-attf-href="http://{{bu.real_build.host}}/runbot/static/build/#{bu['real_build'].dest}/coverage/index.html">Coverage <i class="fa fa-file-text-o"/></a></li>
                     <li t-if="bu.global_state!='pending'" class="divider"></li>
-                    <li><a t-attf-href="{{br['branch'].branch_url}}">Branch or pull <i class="fa fa-github"/></a></li>
+                    <li><a t-attf-href="{{br['branch'].branch_url}}"><t t-esc="'Branch ' if not br['branch'].pull_head_name else 'Pull '"/><i class="fa fa-github"/></a></li>
                     <li><a t-attf-href="https://{{repo.base}}/commit/{{bu['name']}}">Commit <i class="fa fa-github"/></a></li>
                     <li><a t-attf-href="https://{{repo.base}}/compare/{{br['branch'].branch_name}}">Compare <i class="fa fa-github"/></a></li>
                     <!-- TODO branch.pull from -->
@@ -118,54 +111,32 @@
     </template>
     <template id="runbot.build">
         <t t-call='website.layout'>
-            <div class="container" style="width: 100%;">
+
+          <t t-set="nav_form">
+            <form class="form-inline">
+              <div class="btn-group">
+                  <t t-call="runbot.build_button">
+                      <t t-set="bu" t-value="build"/>
+                      <t t-set="klass" t-value="''"/>
+                      <t t-set="show_commit_button" t-value="True"/>
+                  </t>
+              </div>
+            </form>
+            <form class="form-inline" t-attf-action="/runbot/build/#{build['id']}/force" method='POST' t-if="request.params.get('ask_rebuild')" groups="runbot.group_user">
+              <a href='#' class="btn btn-danger runbot-rebuild" t-attf-data-runbot-build="#{build['id']}" > <i class='fa fa-refresh'/> Force Rebuild</a>
+            </form>
+          </t>
                 <div class="row" >
                     <div class='col-md-12'>
-                        <nav class="navbar navbar-default" role="navigation">
-                          <div class="container-fluid">
-                            <div class="navbar-header">
-                              <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
-                                <span class="sr-only">Toggle navigation</span>
-                                <span class="icon-bar"></span>
-                                <span class="icon-bar"></span>
-                                <span class="icon-bar"></span>
-                              </button>
-                              <a class="navbar-brand" t-attf-href="/runbot/repo/#{ slug(repo) }"><b><t t-esc="repo.base"/></b></a>
-                              <a class="navbar-brand" t-attf-href="/runbot/build/{{build['id']}}">
-                                <t t-esc="build['dest']"/>
-                                <t t-call="runbot.build_name">
-                                    <t t-set="bu" t-value="build"/>
-                                </t>
-                              </a>
-                            </div>
-                            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
-                              <form class="navbar-form navbar-left form-inline">
-                                <div class="btn-group">
-                                    <t t-call="runbot.build_button">
-                                        <t t-set="bu" t-value="build"/>
-                                        <t t-set="klass" t-value="''"/>
-                                    </t>
-                                </div>
-                              </form>
-                              <p class="navbar-text">
-                              </p>
-
-                              <form class="navbar-form navbar-left form-inline" t-attf-action="/runbot/build/#{build['id']}/force" method='POST' t-if="request.params.get('ask_rebuild')" groups="runbot.group_user">
-                                <a href='#' class="btn btn-danger runbot-rebuild" t-attf-data-runbot-build="#{build['id']}" > <i class='fa fa-refresh'/> Force Rebuild</a>
-                              </form>
-                            </div>
-                          </div>
-                        </nav>
-                        
                         <table class="table table-condensed tabel-bordered">
                           <tr>
                             <t t-set="rowclass"><t t-call="runbot.build_class"><t t-set="build" t-value="build"/></t></t>
-                            <td t-attf-class="{{rowclass}}">
+                            <td t-attf-class="bg-{{rowclass.strip()}}-light">
                                 Subject: <t t-esc="build['subject']"/><br/>
                                 Author: <t t-esc="build['author']"/><br/>
                                 Committer: <t t-esc="build['committer']"/><br/>
                                 Commit: <a t-attf-href="https://{{build.repo_id.base}}/commit/{{build.name}}"><t t-esc="build.name"/></a><br/>
-                                <t t-foreach="build.dependency_ids" t-as="dep">
+                                <t t-foreach="build.sudo().dependency_ids" t-as="dep">
                                   Dep: <t t-esc="dep.dependecy_repo_id.short_name"/>:<a t-attf-href="https://{{dep.dependecy_repo_id.base}}/commit/{{dep.dependency_hash}}"><t t-esc="dep.dependency_hash"/></a>
                                   <t t-if='dep.closest_branch_id'> from branch <t t-esc="dep.closest_branch_id.name"/></t>
                                   <br/>
@@ -173,26 +144,27 @@
                                 Branch: <span id="branchclp"><t t-esc="build.branch_id.branch_name"/></span>
                                 <a href="#" class="clipbtn octicon octicon-clippy" data-clipboard-target="#branchclp" title="Copy branch name to clipboard"/><br/>
                                 Build host: <t t-esc="build.real_build.host"/><br/>
+                                Build dest: <t t-esc="build['dest']"/><br/>
                             </td>
                             <td t-if="build.real_build.children_ids">
                               Children:
-                              <t t-if="build.real_build.nb_pending > 0"><t t-esc="build.real_build.nb_pending"/> pending </t>
-                              <t t-if="build.real_build.nb_testing > 0"><t t-esc="build.real_build.nb_testing"/> testing </t>
-                              <t t-if="build.real_build.nb_running > 0"><t t-esc="build.real_build.nb_running"/> running </t>
                               <table class="table table-condensed">
                                 <t t-foreach="build.real_build.children_ids.sorted('id')" t-as="child">
                                   <t t-set="rowclass"><t t-call="runbot.build_class"><t t-set="build" t-value="child"/></t></t>
-                                  <tr><td t-attf-class="{{rowclass}}">
+                                  <tr><td t-attf-class="bg-{{rowclass.strip()}}-light">
                                     <a t-attf-href="/runbot/build/{{child.id}}" >Build <t t-esc="child.id"/></a>
-                                    with config <a t-attf-href="/web#id={{child.config_id.id}}&amp;view_type=form&amp;model=runbot.build.config"><t t-esc="child.config_id.name"/></a>
+                                    with config <t t-esc="child.config_id.name"/>
+                                    <a groups="runbot.group_build_config_user" t-attf-href="/web#id={{child.config_id.id}}&amp;view_type=form&amp;model=runbot.build.config">...</a>
                                     <t t-if="child.orphan_result"><i class="fa fa-chain-broken" title="Build result ignored for parent" /></t>
                                     <t t-if="child.job"> Running step: <t t-esc="child.job"/></t>
                                     <t t-if="child.global_state in ['testing', 'waiting']">
                                       <i class="fa fa-spinner fa-spin"/>
                                       <t t-esc="child.global_state"/>
                                     </t>
-                                    <a t-if="child.real_build.local_state=='running'" t-attf-href="http://{{child.domain}}/?db={{child.real_build.dest}}-all" title="Sign in on this build" aria-label="Sign in on this build"><i class="fa fa-sign-in"/></a>
-                                    <a t-if="child.real_build.local_state=='done' and child.real_build.requested_action != 'wake_up'" href="#" t-att-data-runbot-build="child.real_build.id" class="runbot-wakeup" title="Wake up this build" aria-label="Wake up this build"><i class="fa fa-coffee"/></a>
+                                    <t t-call="runbot.build_button">
+                                        <t t-set="bu" t-value="child"/>
+                                        <t t-set="klass" t-value="'btn-group-ssm'"/>
+                                    </t>
 
                                   </td></tr>
                                 </t>
@@ -203,7 +175,7 @@
                         <p t-if="build.parent_id">Child of <a t-attf-href="/runbot/build/#{build.parent_id.id}"><t t-esc="build.parent_id.dest"/></a>
                         <t t-if="build.orphan_result">&amp;nbsp;<i class="fa fa-chain-broken" title="Build result ignored for parent" />&amp;nbsp;Orphaned build, the result does not affect parent build result</t></p>
                         <p t-if="build.duplicate_id">Duplicate of <a t-attf-href="/runbot/build/#{build.duplicate_id.id}"><t t-esc="build.duplicate_id.dest"/></a></p>
-                        <table class="table table-condensed table-striped">
+                        <table class="table table-condensed">
                         <tr>
                             <th>Date</th>
                             <th>Level</th>
@@ -213,13 +185,13 @@
                         <t t-foreach="build.real_build.sudo().log_ids" t-as="l">
                             <t t-set="subbuild" t-value="(([child for child in build.real_build.children_ids if child.id == int(l.path)] if l.type == 'subbuild' else False) or [build.browse()])[0]"/>
                             <t t-set="logclass" t-value="dict(CRITICAL='danger', ERROR='danger', WARNING='warning', OK='success', SEPARATOR='separator').get(l.level)"/>
-                            <tr t-att-class="logclass">
+                            <tr t-attf-class="bg-{{logclass}}-light">
                                 <td style="white-space: nowrap; width:1%;"><t t-esc="l.create_date"/></td>
                                 <td style="white-space: nowrap; width:1%;"><b t-if="l.level != 'SEPARATOR' and l.type != 'link'" t-esc="l.level"/></td>
                                 <td style="white-space: nowrap; width:1%;"><t t-if="l.level != 'SEPARATOR' and l.type != 'link'" t-esc="l.type"/></td>
                                 <t t-set="message_class" t-value="''"/>
                                 <t t-if="subbuild" t-set="message_class"><t t-call="runbot.build_class"><t t-set="build" t-value="subbuild"/></t></t>
-                                <td t-att-class="message_class">
+                                <td t-attf-class="bg-{{message_class.strip() or logclass}}-light">
                                     <t t-if="l.type not in ('runbot', 'link')">
                                       <t t-if="l.type == 'subbuild'">
                                          <a t-attf-href="/runbot/build/{{l.path}}">Build #<t t-esc="l.path"/></a>
@@ -241,7 +213,7 @@
                                       <t t-if="l.type == 'subbuild' and subbuild.sudo().error_log_ids">
                                          <a data-toggle="collapse" t-attf-data-target="#subbuild-{{subbuild.id}}"><i class="fa"></i></a>
                                          <div t-attf-id="subbuild-{{subbuild.id}}" class="collapse in">
-                                          <table class="table table-condensed table-striped" style="margin-bottom:0;">
+                                          <table class="table table-condensed" style="margin-bottom:0;">
                                             <t t-foreach="subbuild.sudo().error_log_ids" t-as="sl">
                                               <tr>
                                                 <td t-att-class="dict(CRITICAL='danger', ERROR='danger', WARNING='warning', OK='success', SEPARATOR='separator').get(sl.level)">
@@ -262,7 +234,6 @@
                         </t>
                         </table>
                     </div>
-                </div>
             </div>
         </t>
     </template>
diff --git a/runbot/templates/dashboard.xml b/runbot/templates/dashboard.xml
index da5630b3..07394522 100644
--- a/runbot/templates/dashboard.xml
+++ b/runbot/templates/dashboard.xml
@@ -141,21 +141,7 @@
           <div class="row">
             <div class="col-md-12">
               <div>
-                <span t-attf-class="label label-{{pending_level}}">Pending: <t t-esc="pending_total"/></span>
-                 <t t-set="testing">0</t>
-                 <t t-set="workers">0</t>
-
-                 <t t-foreach="hosts_data.sorted(key=lambda h:h.name)" t-as="host">
-                  <t t-set="testing" t-value="int(testing) + host.nb_testing"/>
-                  <t t-set="workers" t-value="int(workers) + host.sudo().get_nb_worker()"/>
-                 </t>
-                 <t t-set="klass">success</t>
-
-                  <t t-if="int(testing)/workers > 0"><t t-set="klass">info</t></t>
-                  <t t-if="int(testing)/workers > 0.75"><t t-set="klass">warning</t></t>
-                  <t t-if="int(testing)/workers >= 1"><t t-set="klass">danger</t></t>
-
-                <span t-attf-class="label label-{{klass}}">Testing: <t t-esc="testing"/>/<t t-esc="workers"/></span>
+               <t t-call="slots_infos"/>
               </div>
               <t t-foreach="glances_data.keys()" t-as="repo">
                 <div>
@@ -178,9 +164,9 @@
                   <t t-if="host.nb_testing > host.sudo().get_nb_worker()"><t t-set="klass">danger</t></t>
                   <span t-attf-class="label label-{{klass}}"><span t-esc="host.nb_testing"/>/<span t-esc="host.sudo().get_nb_worker()"/></span>
                   <t t-esc="host.nb_running"/>
-                  <t t-set="succes_time" t-value="int(datetime.datetime.now().timestamp() - datetime.datetime.strptime(host.last_success, '%Y-%m-%d %H:%M:%S').timestamp())"/>
-                  <t t-set="start_time" t-value="int(datetime.datetime.now().timestamp() - datetime.datetime.strptime(host.last_start_loop, '%Y-%m-%d %H:%M:%S').timestamp())"/>
-                  <t t-set="end_time" t-value="int(datetime.datetime.now().timestamp() - datetime.datetime.strptime(host.last_end_loop, '%Y-%m-%d %H:%M:%S').timestamp())"/>
+                  <t t-set="succes_time" t-value="int(datetime.datetime.now().timestamp() - host.last_success.timestamp())"/>
+                  <t t-set="start_time" t-value="int(datetime.datetime.now().timestamp() - host.last_start_loop.timestamp())"/>
+                  <t t-set="end_time" t-value="int(datetime.datetime.now().timestamp() - host.last_end_loop.timestamp())"/>
 
                   <t t-set="klass">success</t>
                   <t t-if="succes_time > 30"><t t-set="klass">info</t></t>
diff --git a/runbot/templates/frontend.xml b/runbot/templates/frontend.xml
index 943a4650..c7a29692 100644
--- a/runbot/templates/frontend.xml
+++ b/runbot/templates/frontend.xml
@@ -2,13 +2,48 @@
 <odoo>
     <data>
       <!-- Replace default menu ( Home / Contactus and co...) with 5 first repos) -->
-      <template id="inherits_branch_in_menu" inherit_id="website.layout" name="Inherits Show top 5 branches in menu">
-          <xpath expr="//t[@t-foreach=&quot;website.menu_id.child_id&quot;][@t-as=&quot;submenu&quot;]" position="replace">
-              <t t-if="repos" >
-                   <t t-foreach="repos[:5]" t-as="re">
-                       <li><a t-attf-href="/runbot/repo/{{slug(re)}}?search={{request.params.get('search', '')}}"><i class='fa fa-github' /> <t t-esc="re.short_name"/></a></li>
-                   </t>
-              </t>
+      <template id="inherits_branch_in_menu" inherit_id="website.layout" name="Inherits Show top 6 repo in menu and dropdown">
+          <xpath expr="//footer" position="replace">
+          </xpath>
+          <xpath expr="//nav" position="replace">
+            <nav class="navbar navbar-expand-md navbar-light bg-light">
+                  <a t-if="repo" t-attf-href="/runbot/repo/{{slug(repo)}}?search={{request.params.get('search', '')}}">
+                    <b style="color:#777;"><t t-esc="repo.short_name"/></b>
+                  </a>
+                  <button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#top_menu_collapse">
+                      <span class="navbar-toggler-icon"/>
+                  </button>
+                  <div class="collapse navbar-collapse" id="top_menu_collapse">
+                      <ul class="nav navbar-nav ml-auto text-right" id="top_menu">
+                         <t t-if="repos" >
+                              <t t-foreach="repos[:6]" t-as="re">
+                                  <li ><a t-attf-href="/runbot/repo/{{slug(re)}}?search={{request.params.get('search', '')}}"><i class='fa fa-github' /> <t t-esc="re.short_name"/></a></li>
+                              </t>
+                              <li t-if="len(repos)>6" class="dropdown">
+                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-plus"/></a>
+                                <ul class="dropdown-menu">
+                                  <t t-foreach='repos[6:]' t-as='re'>
+                                      <li><a t-attf-href="/runbot/repo/{{slug(re)}}"><t t-esc="re.short_name"/></a></li>
+                                  </t>
+                                </ul>
+                              </li>
+                          </t>
+                          <li class="nav-item divider" t-ignore="true" t-if="not user_id._is_public()"/>
+                          <li class="nav-item dropdown" t-ignore="true" t-if="not user_id._is_public()">
+                              <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown">
+                                  <b>
+                                      <span t-esc="user_id.name[:23] + '...' if user_id.name and len(user_id.name) &gt; 25 else user_id.name"/>
+                                  </b>
+                              </a>
+                              <div class="dropdown-menu js_usermenu" role="menu">
+                                  <a id="o_logout" class="dropdown-item" t-attf-href="/web/session/logout?redirect=/" role="menuitem">Logout</a>
+                              </div>
+                          </li>
+                      </ul>
+                      <t t-raw="nav_form or ''">
+                      </t>
+                  </div>
+            </nav>
           </xpath>
       </template>
 
@@ -21,6 +56,18 @@
               <attribute name="t-value">'o_connected_user' if env['ir.ui.view'].user_has_groups('base.group_website_publisher') else None</attribute>
           </xpath>
       </template>
+
+      <template id="runbot.slots_infos" name="Hosts slot nb pending/testing/slots">
+        <span t-attf-class="label label-{{pending_level}}">Pending: <t t-esc="pending_total"/></span>
+        <t t-set="testing" t-value="hosts_data._total_testing()"/>
+        <t t-set="workers" t-value="hosts_data._total_workers()"/>
+        <t t-set="klass">success</t>
+        <t t-if="int(testing)/workers > 0"><t t-set="klass">info</t></t>
+        <t t-if="int(testing)/workers > 0.75"><t t-set="klass">warning</t></t>
+        <t t-if="int(testing)/workers >= 1"><t t-set="klass">danger</t></t>
+        <span t-attf-class="label label-{{klass}}">Testing: <t t-esc="testing"/>/<t t-esc="workers"/></span>
+      </template>
+
       <!-- Frontend repository block -->
       <template id="runbot.repo">
           <t t-call='website.layout'>
@@ -28,58 +75,20 @@
                   <t t-if="refresh">
                       <meta http-equiv="refresh" t-att-content="refresh"/>
                   </t>
-                  <style>
-                    .killed {
-                      background-color: #aaa;
-                    }
-                  </style>
+              </t>
+              <t t-set="nav_form">
+                <form class="form-inline my-2 my-lg-0" role="search" t-att-action="qu(search='')" method="get">
+                  <div class="input-group md-form form-sm form-2 pl-0">
+                    <input class="form-control my-0 py-1 red-border" type="text" placeholder="Search" aria-label="Search" name="search" t-att-value="search"/>
+                    <div class="input-group-append">
+                      <button type='submit' class="input-group-text red lighten-3" id="basic-text1"><i class="fa fa-search text-grey"></i></button>
+                    </div>
+                  </div>
+                </form>
               </t>
               <div class="container-fluid">
                   <div class="row">
                       <div class='col-md-12'>
-                          <nav class="navbar navbar-default" role="navigation">
-                            <div class="container-fluid">
-                              <div class="navbar-header">
-                                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
-                                  <span class="sr-only">Toggle navigation</span>
-                                  <span class="icon-bar"></span>
-                                  <span class="icon-bar"></span>
-                                  <span class="icon-bar"></span>
-                                </button>
-                                <t t-if="repo">
-                                    <ul class="nav navbar-nav">
-                                      <li class="dropdown">
-                                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><b style="font-size: 18px;"><t t-esc="repo.base"/></b><b class="caret"></b></a>
-                                        <ul class="dropdown-menu">
-                                          <t t-foreach='repos' t-as='re'>
-                                              <li><a t-attf-href="/runbot/repo/{{slug(re)}}"><t t-esc="re.base"/></a></li>
-                                          </t>
-                                        </ul>
-                                      </li>
-                                    </ul>
-
-                                </t>
-                              </div>
-                              <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
-                                <t t-if="repo">
-                                  <form class="navbar-form navbar-right" role="search" t-att-action="qu(search='')" method="get">
-                                    <div class="form-group">
-                                      <input type="search" name="search" class="form-control" placeholder="Search" t-att-value="search"/>
-                                      <button type="submit" class="btn btn-default">Search</button>
-                                    </div>
-                                  </form>
-                                </t>
-                              </div>
-                              <p class="text-center">
-                                  <t  t-foreach="host_stats" t-as="hs">
-                                  <span class="label label-default">
-                                      <t t-esc="hs['host']"/>: <t t-esc="hs['testing']"/> testing
-                                  </span>&amp;nbsp;
-                                  </t>
-                                  <span t-attf-class="label label-{{pending_level}}">Pending: <t t-esc="pending_total"/></span>
-                              </p>
-                            </div>
-                          </nav>
                           <div t-if="message" class="alert alert-warning" role="alert">
                             <t t-esc="message" />
                           </div>
@@ -90,21 +99,16 @@
                           <table t-if="repo" class="table table-condensed table-bordered" style="table-layout: initial;">
                           <tr>
                               <th>Branch</th>
-                              <td colspan="4" class="text-right">
-                                  <t t-esc="repo.base"/>: 
-                                  <t t-esc="testing"/> testing,
-                                  <t t-esc="running"/> running,
-                                  <t t-esc="pending"/> pending.
+                              <td colspan="4">
+                                <span class="pull-right" t-call="runbot.slots_infos"/>
                               </td>
-
                           </tr>
                           <tr t-foreach="branches" t-as="br">
-                              <td>
-                                  <i t-if="br['branch'].sticky" class="fa fa-star" style="color: #f0ad4e" />
-                                  <a t-attf-href="/runbot/branch/#{br['branch'].id}"><b t-esc="br['branch'].branch_name"/></a>
-                                  <small><t t-esc="br['builds'] and br['builds'][0].get_formated_build_time()"/></small><br/>
-                                  <div class="btn-group btn-group-xs">
-                                      <a t-attf-href="{{br['branch'].branch_url}}" class="btn btn-default btn-xs">Branch or pull <i class="fa fa-github"/></a>
+                              <td style="width:12%">
+                                  <small class="branch_time" ><t t-esc="br['builds'] and br['builds'][0].get_formated_build_time()"/></small>
+                                  <div class="branch_name"><i t-if="br['branch'].sticky" class="fa fa-star" style="color: #f0ad4e" /><a t-attf-href="/runbot/branch/#{br['branch'].id}"><b t-esc="br['branch'].branch_name"/></a></div>
+                                         <div class="btn-group btn-group-xs">
+                                      <a t-attf-href="{{br['branch'].branch_url}}" class="btn btn-default btn-xs"><t t-esc="'Branch ' if not br['branch'].pull_head_name else 'Pull '"/><i class="fa fa-github"/></a>
                                       <a t-attf-href="/runbot/quick_connect/#{br['branch'].id}" class="btn btn-default btn-xs" aria-label="Quick Connect"><i class="fa fa-fast-forward" title="Quick Connect"/></a>
                                   </div>
                                   <t t-if="br['branch'].sticky">
@@ -134,20 +138,21 @@
                                     <t t-if="bu.global_result == 'skipped'"><t t-set="klass">default</t></t>
                                     <t t-if="bu.global_result in ['killed', 'manually_killed']"><t t-set="klass">killed</t></t>
                                   </t>
-                                  <td t-attf-class="{{klass}}">
+                                  <td t-attf-class="bg-{{klass}}-light" style="width:22%">
                                      <t t-call="runbot.build_button">
                                       <t t-set="klass">btn-group-sm</t>
                                       <t t-set="show_rebuild_button" t-value="bu==br['builds'][0]"></t>
+                                      <t t-set="show_commit_button" t-value="True"/>
                                      </t>
                                      <t t-if="bu['build_type']=='scheduled'"><i class="fa fa-moon-o" t-att-title="bu.build_type_label()" t-att-aria-label="bu.build_type_label()"/></t>
                                      <t t-if="bu['build_type'] in ('rebuild', 'indirect')"><i class="fa fa-recycle" t-att-title="bu.build_type_label()" t-att-aria-label="bu.build_type_label()"/></t>
-                                     <t t-if="bu['subject']">
+                                     <span t-if="bu['subject']" class="build_subject">
                                           <t t-if="bu.config_id != bu.branch_id.config_id">
                                             <b t-esc="bu.config_id.name"/>
                                           </t>
                                           <span t-esc="bu['subject'][:32] + ('...' if bu['subject'][32:] else '') " t-att-title="bu['subject']"/>
                                            <br/>
-                                      </t>
+                                     </span>
                                      <t t-id="bu['author']">
                                           <t t-esc="bu['author']"/>
                                           <t t-if="bu['committer'] and bu['author'] != bu['committer']" t-id="bu['committer']">
diff --git a/runbot/tests/common.py b/runbot/tests/common.py
index fbc34973..24571649 100644
--- a/runbot/tests/common.py
+++ b/runbot/tests/common.py
@@ -39,6 +39,8 @@ class RunbotCase(TransactionCase):
         self.start_patcher('docker_build', 'odoo.addons.runbot.models.build.docker_build')
         self.start_patcher('docker_ps', 'odoo.addons.runbot.models.repo.docker_ps', [])
         self.start_patcher('docker_stop', 'odoo.addons.runbot.models.repo.docker_stop')
+        self.start_patcher('cr_commit', 'odoo.sql_db.Cursor.commit', None)
+        self.start_patcher('repo_commit', 'odoo.addons.runbot.models.repo.runbot_repo._commit', None)
 
     def start_patcher(self, patcher_name, patcher_path, return_value=Dummy, side_effect=Dummy):
         patcher = patch(patcher_path)
diff --git a/runbot/tests/test_branch.py b/runbot/tests/test_branch.py
index 5bd531d3..6c5ac680 100644
--- a/runbot/tests/test_branch.py
+++ b/runbot/tests/test_branch.py
@@ -120,7 +120,7 @@ class TestBranchRelations(RunbotCase):
         self.assertEqual(b.previous_version.branch_name, '13.0')
         self.assertEqual(sorted(b.intermediate_stickies.mapped('branch_name')), ['saas-13.1', 'saas-13.2'])
 
-        b.closest_sticky = self.last
+        b.defined_sticky = self.last
 
         self.assertEqual(b.closest_sticky.branch_name, 'saas-13.2')
         self.assertEqual(b.previous_version.branch_name, '13.0')
diff --git a/runbot/tests/test_build.py b/runbot/tests/test_build.py
index 5a7ae37a..1d6974a8 100644
--- a/runbot/tests/test_build.py
+++ b/runbot/tests/test_build.py
@@ -327,68 +327,66 @@ class Test_Build(RunbotCase):
             'extra_params': '5',
         })
 
-        def assert_state(nb_pending, nb_testing, nb_running, global_state, build):
-            self.assertEqual(build.nb_pending, nb_pending)
-            self.assertEqual(build.nb_testing, nb_testing)
-            self.assertEqual(build.nb_running, nb_running)
+        def assert_state(global_state, build):
             self.assertEqual(build.global_state, global_state)
 
-        assert_state(5, 0, 0, 'pending', build1)
-        assert_state(3, 0, 0, 'pending', build1_1)
-        assert_state(1, 0, 0, 'pending', build1_2)
-        assert_state(1, 0, 0, 'pending', build1_1_1)
-        assert_state(1, 0, 0, 'pending', build1_1_2)
+        assert_state('pending', build1)
+        assert_state('pending', build1_1)
+        assert_state('pending', build1_2)
+        assert_state('pending', build1_1_1)
+        assert_state('pending', build1_1_2)
 
         build1.local_state = 'testing'
         build1_1.local_state = 'testing'
         build1.local_state = 'done'
         build1_1.local_state = 'done'
 
-        assert_state(3, 0, 0, 'waiting', build1)
-        assert_state(2, 0, 0, 'waiting', build1_1)
-        assert_state(1, 0, 0, 'pending', build1_2)
-        assert_state(1, 0, 0, 'pending', build1_1_1)
-        assert_state(1, 0, 0, 'pending', build1_1_2)
+        assert_state('waiting', build1)
+        assert_state('waiting', build1_1)
+        assert_state('pending', build1_2)
+        assert_state('pending', build1_1_1)
+        assert_state('pending', build1_1_2)
 
         build1_1_1.local_state = 'testing'
 
-        assert_state(2, 1, 0, 'waiting', build1)
-        assert_state(1, 1, 0, 'waiting', build1_1)
-        assert_state(1, 0, 0, 'pending', build1_2)
-        assert_state(0, 1, 0, 'testing', build1_1_1)
-        assert_state(1, 0, 0, 'pending', build1_1_2)
+        assert_state('waiting', build1)
+        assert_state('waiting', build1_1)
+        assert_state('pending', build1_2)
+        assert_state('testing', build1_1_1)
+        assert_state('pending', build1_1_2)
 
         build1_2.local_state = 'testing'
 
-        assert_state(1, 2, 0, 'waiting', build1)
-        assert_state(1, 1, 0, 'waiting', build1_1)
-        assert_state(0, 1, 0, 'testing', build1_2)
-        assert_state(0, 1, 0, 'testing', build1_1_1)
-        assert_state(1, 0, 0, 'pending', build1_1_2)
+        assert_state('waiting', build1)
+        assert_state('waiting', build1_1)
+        assert_state('testing', build1_2)
+        assert_state('testing', build1_1_1)
+        assert_state('pending', build1_1_2)
 
         build1_2.local_state = 'testing'  # writing same state a second time
 
-        assert_state(1, 2, 0, 'waiting', build1)
-        assert_state(1, 1, 0, 'waiting', build1_1)
-        assert_state(0, 1, 0, 'testing', build1_2)
-        assert_state(0, 1, 0, 'testing', build1_1_1)
-        assert_state(1, 0, 0, 'pending', build1_1_2)
+        assert_state('waiting', build1)
+        assert_state('waiting', build1_1)
+        assert_state('testing', build1_2)
+        assert_state('testing', build1_1_1)
+        assert_state('pending', build1_1_2)
 
         build1_1_2.local_state = 'done'
         build1_1_1.local_state = 'done'
         build1_2.local_state = 'done'
 
-        assert_state(0, 0, 0, 'done', build1)
-        assert_state(0, 0, 0, 'done', build1_1)
-        assert_state(0, 0, 0, 'done', build1_2)
-        assert_state(0, 0, 0, 'done', build1_1_1)
-        assert_state(0, 0, 0, 'done', build1_1_2)
+        assert_state('done', build1)
+        assert_state('done', build1_1)
+        assert_state('done', build1_2)
+        assert_state('done', build1_1_1)
+        assert_state('done', build1_1_2)
 
     def test_duplicate_childrens(self):
         build_old = self.create_build({
             'branch_id': self.branch_10.id,
             'name': 'd0d0caca0000ffffffffffffffffffffffffffff',
             'extra_params': '0',
+            'local_state': 'done'
         })
         build_parent = self.create_build({
             'branch_id': self.branch_10.id,
@@ -404,8 +402,7 @@ class Test_Build(RunbotCase):
         build_parent.local_state = 'done'
         self.assertEqual(build_child.local_state, 'duplicate')
         self.assertEqual(build_child.duplicate_id, build_old)
-        self.assertEqual(build_parent.nb_pending, 0)
-        self.assertEqual(build_parent.nb_testing, 0)
+        self.assertEqual(build_child.global_state, 'done')
         self.assertEqual(build_parent.global_state, 'done')
 
 
@@ -734,6 +731,10 @@ class TestClosestBranch(RunbotCase):
             'repo_id': self.community_repo.id,
             'name': 'refs/pull/123456'
         })
+
+        # trigger compute and ensure that mock_github is used. (using correct side effect would work too)
+        self.assertEqual(server_pr.pull_head_name, 'foo-dev:bar_branch')
+
         mock_github.return_value = {
             'head': {'label': 'foo-dev:foobar_branch'},
             'base': {'ref': '10.0'},
@@ -743,6 +744,8 @@ class TestClosestBranch(RunbotCase):
             'repo_id': self.enterprise_repo.id,
             'name': 'refs/pull/789101'
         })
+        self.assertEqual(addons_pr.pull_head_name, 'foo-dev:foobar_branch')
+        closest = addons_pr._get_closest_branch(self.community_repo.id)
         self.assertEqual((self.branch_odoo_10, 'pr_target'), addons_pr._get_closest_branch(self.community_repo.id))
 
     def test_closest_branch_05_master(self):
diff --git a/runbot/tests/test_build_error.py b/runbot/tests/test_build_error.py
index bb68513b..0d94245f 100644
--- a/runbot/tests/test_build_error.py
+++ b/runbot/tests/test_build_error.py
@@ -92,6 +92,7 @@ class TestBuildError(RunbotCase):
         self.assertIn(ko_build_new, new_build_error.build_ids, 'The parsed build with a re-apearing error should generate a new runbot.build.error')
         self.assertIn(build_error, new_build_error.error_history_ids, 'The old error should appear in history')
 
+
     def test_build_error_links(self):
         build_a = self.create_test_build({'local_result': 'ko'})
         build_b = self.create_test_build({'local_result': 'ko'})
@@ -111,8 +112,7 @@ class TestBuildError(RunbotCase):
         #  test that the random bug is parent when linking errors
         all_errors = error_a | error_b
         all_errors.link_errors()
-
-        self.assertIn(error_b.child_ids, error_a, 'Random error should be the parent')
+        self.assertEqual(error_b.child_ids, error_a, 'Random error should be the parent')
 
         #  Test that changing bug resolution is propagated to children
         error_b.active = True
diff --git a/runbot/tests/test_frontend.py b/runbot/tests/test_frontend.py
index 5c81f706..a89a599b 100644
--- a/runbot/tests/test_frontend.py
+++ b/runbot/tests/test_frontend.py
@@ -53,9 +53,6 @@ class Test_Frontend(RunbotCase):
             self.assertEqual(self.sticky_branch, context['branches'][0]['branch'], "The sticky branch should be in first place")
             self.assertEqual(self.branch, context['branches'][1]['branch'], "The non sticky branch should be in second place")
             self.assertEqual(len(context['branches'][0]['builds']), 4, "Only the 4 last builds should appear in the context")
-            self.assertEqual(context['pending'], 2, "There should be 2 pending builds")
-            self.assertEqual(context['running'], 2, "There should be 2 running builds")
-            self.assertEqual(context['testing'], 2, "There should be 2 testing builds")
             self.assertEqual(context['pending_total'], 2, "There should be 2 pending builds")
             self.assertEqual(context['pending_level'], 'info', "The pending level should be info")
             return Response()
diff --git a/runbot/tests/test_repo.py b/runbot/tests/test_repo.py
index 92690100..5111de2f 100644
--- a/runbot/tests/test_repo.py
+++ b/runbot/tests/test_repo.py
@@ -176,20 +176,22 @@ class Test_Repo(RunbotCase):
 
         _logger.info('Create pending builds took: %ssec', (time.time() - inserted_time))
 
+
+    @common.warmup
     def test_times(self):
-        def _test_times(model, field_name):
+        def _test_times(model, setter, field_name):
             repo1 = self.Repo.create({'name': 'bla@example.com:foo/bar'})
             repo2 = self.Repo.create({'name': 'bla@example.com:foo2/bar2'})
             count = self.cr.sql_log_count
-            repo1[field_name] = 1.1
-            self.assertEqual(self.cr.sql_log_count - count, 1, "Only one insert should have been triggered")
-            repo2[field_name] = 1.2
+            with self.assertQueryCount(1):
+                getattr(repo1, setter)(1.1)
+            getattr(repo2, setter)(1.2)
             self.assertEqual(len(self.env[model].search([])), 2)
             self.assertEqual(repo1[field_name], 1.1)
             self.assertEqual(repo2[field_name], 1.2)
 
-            repo1[field_name] = 1.3
-            repo2[field_name] = 1.4
+            getattr(repo1, setter)(1.3)
+            getattr(repo2, setter)(1.4)
 
             self.assertEqual(len(self.env[model].search([])), 4)
             self.assertEqual(repo1[field_name], 1.3)
@@ -205,8 +207,8 @@ class Test_Repo(RunbotCase):
             self.assertEqual(repo1[field_name], 1.3)
             self.assertEqual(repo2[field_name], 1.4)
 
-        _test_times('runbot.repo.hooktime', 'hook_time')
-        _test_times('runbot.repo.reftime', 'get_ref_time')
+        _test_times('runbot.repo.hooktime', 'set_hook_time', 'hook_time')
+        _test_times('runbot.repo.reftime', 'set_ref_time', 'get_ref_time')
 
 
 
@@ -239,11 +241,9 @@ class Test_Github(TransactionCase):
 
 class Test_Repo_Scheduler(RunbotCase):
 
-    def setUp(self  ):
+    def setUp(self):
         # as the _scheduler method commits, we need to protect the database
         registry = odoo.registry()
-        registry.enter_test_mode()
-        self.addCleanup(registry.leave_test_mode)
         super(Test_Repo_Scheduler, self).setUp()
 
         self.fqdn_patcher = patch('odoo.addons.runbot.models.host.fqdn')
@@ -261,6 +261,7 @@ class Test_Repo_Scheduler(RunbotCase):
     @patch('odoo.addons.runbot.models.build.runbot_build._schedule')
     @patch('odoo.addons.runbot.models.build.runbot_build._init_pendings')
     def test_repo_scheduler(self, mock_init_pendings, mock_schedule, mock_kill):
+
         self.env['ir.config_parameter'].set_param('runbot.runbot_workers', 6)
         builds = []
         # create 6 builds that are testing on the host to verify that
@@ -305,7 +306,3 @@ class Test_Repo_Scheduler(RunbotCase):
         self.Build.search([('name', '=', 'a')]).write({'local_state': 'done'})
 
         self.foo_repo._scheduler(host)
-        build.invalidate_cache()
-        scheduled_build.invalidate_cache()
-        self.assertEqual(build.host, 'host.runbot.com')
-        self.assertFalse(scheduled_build.host)
diff --git a/runbot/tests/test_schedule.py b/runbot/tests/test_schedule.py
index 878e4479..59c79397 100644
--- a/runbot/tests/test_schedule.py
+++ b/runbot/tests/test_schedule.py
@@ -11,8 +11,6 @@ class TestSchedule(RunbotCase):
     def setUp(self):
         # entering test mode to avoid that the _schedule method commits records
         registry = odoo.registry()
-        registry.enter_test_mode()
-        self.addCleanup(registry.leave_test_mode)
         super(TestSchedule, self).setUp()
 
         self.repo = self.Repo.create({'name': 'bla@example.com:foo/bar'})
diff --git a/runbot/views/assets.xml b/runbot/views/assets.xml
index 15a22832..323f6f70 100644
--- a/runbot/views/assets.xml
+++ b/runbot/views/assets.xml
@@ -3,7 +3,7 @@
     <data>
         <template id="assets_front_end" inherit_id="web.assets_frontend" name="runbot assets">
             <xpath expr="." position="inside">
-                <link rel="stylesheet" href="/runbot/static/src/less/runbot.less"/>
+                <link rel="stylesheet" href="/runbot/static/src/css/runbot.css"/>
             </xpath>
         </template>
     </data>
diff --git a/runbot/views/build_error_views.xml b/runbot/views/build_error_views.xml
index 67ac339a..e8bea2ea 100644
--- a/runbot/views/build_error_views.xml
+++ b/runbot/views/build_error_views.xml
@@ -121,7 +121,7 @@
           <filter string="Fixed" name="fixed_errors" domain="[('active', '=', False)]"/>
           <filter string="Not Fixed" name="not_fixed_errors" domain="[('active', '=', True)]"/>
           <separator/>
-          <filter string="Not Asigned" name="not_assigned_errors" domain="[('responsible', '=', False)]"/>
+          <filter string="Not Assigned" name="not_assigned_errors" domain="[('responsible', '=', False)]"/>
         </search>
       </field>
     </record>
diff --git a/runbot/views/build_views.xml b/runbot/views/build_views.xml
index 757f9072..6bf97f02 100644
--- a/runbot/views/build_views.xml
+++ b/runbot/views/build_views.xml
@@ -82,21 +82,19 @@
                 <field name="name"/>
                 <field name="global_state"/>
                 <field name="dest"/>
-                <separator/>
-                <filter string="Pending" domain="[('global_state','=', 'pending')]"/>
-                <filter string="Testing" domain="[('global_state','in', ('testing', 'waiting'))]"/>
-                <filter string="Running" domain="[('global_state','=', 'running')]"/>
-                <filter string="Done" domain="[('global_state','=','done')]"/>
-                <filter string="Duplicate" domain="[('local_state','=', 'duplicate')]"/>
-                <separator />
+                <filter string="Pending" name='pending' domain="[('global_state','=', 'pending')]"/>
+                <filter string="Testing" name='testing' domain="[('global_state','in', ('testing', 'waiting'))]"/>
+                <filter string="Running" name='running' domain="[('global_state','=', 'running')]"/>
+                <filter string="Done" name='done' domain="[('global_state','=','done')]"/>
+                <filter string="Duplicate" name='duplicate' domain="[('local_state','=', 'duplicate')]"/>
                 <group expand="0" string="Group By...">
-                    <filter string="Repo" domain="[]" context="{'group_by':'repo_id'}"/>
-                    <filter string="Branch" domain="[]" context="{'group_by':'branch_id'}"/>
-                    <filter string="Status" domain="[]" context="{'group_by':'global_state'}"/>
-                    <filter string="Result" domain="[]" context="{'group_by':'global_result'}"/>
-                    <filter string="Start" domain="[]" context="{'group_by':'job_start'}"/>
-                    <filter string="Host" domain="[]" context="{'group_by':'host'}"/>
-                    <filter string="Create Date" domain="[]" context="{'group_by':'create_date'}"/>
+                    <filter string="Repo" name='repo' domain="[]" context="{'group_by':'repo_id'}"/>
+                    <filter string="Branch" name='branch' domain="[]" context="{'group_by':'branch_id'}"/>
+                    <filter string="Status" name='status' domain="[]" context="{'group_by':'global_state'}"/>
+                    <filter string="Result" name='result' domain="[]" context="{'group_by':'global_result'}"/>
+                    <filter string="Start" name='start' domain="[]" context="{'group_by':'job_start'}"/>
+                    <filter string="Host" name='host' domain="[]" context="{'group_by':'host'}"/>
+                    <filter string="Create Date" name='create_date' domain="[]" context="{'group_by':'create_date'}"/>
                 </group>
             </search>
         </field>
@@ -105,7 +103,6 @@
         <field name="name">Builds</field>
         <field name="type">ir.actions.act_window</field>
         <field name="res_model">runbot.build</field>
-        <field name="view_type">form</field>
         <field name="view_mode">tree,form,graph,pivot</field>
     </record>
     <menuitem id="menu_build" action="action_build" parent="runbot_menu_root"/>
diff --git a/runbot/views/config_views.xml b/runbot/views/config_views.xml
index 6f5fe185..75b39b92 100644
--- a/runbot/views/config_views.xml
+++ b/runbot/views/config_views.xml
@@ -23,6 +23,7 @@
                         <field name="update_github_state" groups="base.group_no_one"/>
                         <field name="protected" groups="base.group_no_one"/>
                         <field name="group" groups="base.group_no_one"/>
+                        <field name="monitoring_view_id" groups="base.group_no_one"/>
                     </group>
                 </sheet>
                 <div class="oe_chatter">
@@ -113,7 +114,7 @@
         <search string="Search config">
           <field name="name"/>
           <field name="group_name"/>
-          <filter string="Is in a group" domain="[(['group', '!=', False])]"/>
+          <filter string="Is in a group" name='is_in_group' domain="[(['group', '!=', False])]"/>
           <filter string="No step's defined" name="no_step" domain="[(['step_order_ids', '=', False])]"/>
         </search>
       </field>
@@ -126,12 +127,12 @@
         <search string="Search config step">
           <field name="name"/>
           <field name="group_name"/>
-          <filter string="Install job" domain="[(['job_type', '=', 'install_odoo'])]"/>
-          <filter string="Run job" domain="[(['job_type', '=', 'run_odoo'])]"/>
-          <filter string="Python job" domain="[(['job_type', '=', 'python'])]"/>
-          <filter string="Create job" domain="[(['job_type', '=', 'create_build'])]"/>
+          <filter string="Install job" name='install_job' domain="[(['job_type', '=', 'install_odoo'])]"/>
+          <filter string="Run job" name='run_job' domain="[(['job_type', '=', 'run_odoo'])]"/>
+          <filter string="Python job" name='python_job' domain="[(['job_type', '=', 'python'])]"/>
+          <filter string="Create job" name='create_job' domain="[(['job_type', '=', 'create_build'])]"/>
           <separator/>
-          <filter string="Is in a group" domain="[(['group', '!=', False])]"/>
+          <filter string="Is in a group" name='is_in_group' domain="[(['group', '!=', False])]"/>
           <separator/>
           <filter string="No config defined" name="no_step" domain="[(['step_order_ids', '=', False])]"/>
         </search>
diff --git a/runbot/views/host_views.xml b/runbot/views/host_views.xml
index bfbd09b5..ae784994 100644
--- a/runbot/views/host_views.xml
+++ b/runbot/views/host_views.xml
@@ -16,8 +16,6 @@
                         <field name="last_success" readonly='1'/>
                         <field name="assigned_only"/>
                         <field name="nb_worker"/>
-                        <field name="nb_testing"/>
-                        <field name="nb_running"/>
                         <field name="last_exception" readonly='1'/>
                         <field name="exception_count" readonly='1'/>
                     </group>
diff --git a/runbot/wizards/multi_build_wizard.py b/runbot/wizards/multi_build_wizard.py
index 34fe13d1..707180f7 100644
--- a/runbot/wizards/multi_build_wizard.py
+++ b/runbot/wizards/multi_build_wizard.py
@@ -6,6 +6,7 @@ from odoo import fields, models, api
 class MultiBuildWizard(models.TransientModel):
 
     _name = 'runbot.build.config.multi.wizard'
+    _description = "Multi wizard"
 
     base_name = fields.Char('Generic name', required=True)
     prefix = fields.Char('Prefix', help="Leave blank to use login.")
diff --git a/runbot/wizards/mutli_build_wizard_views.xml b/runbot/wizards/mutli_build_wizard_views.xml
index e7f81da5..9741b962 100644
--- a/runbot/wizards/mutli_build_wizard_views.xml
+++ b/runbot/wizards/mutli_build_wizard_views.xml
@@ -30,7 +30,6 @@
             <field name="name">Generate Multi Build Config</field>
             <field name="type">ir.actions.act_window</field>
             <field name="res_model">runbot.build.config.multi.wizard</field>
-            <field name="view_type">form</field>
             <field name="view_mode">form</field>
             <field name="view_id" ref="runbot_multi_build_wizard_form"/>
             <field name="target">new</field>
diff --git a/runbot_builder/builder.py b/runbot_builder/builder.py
index 5c397347..a4ce66c2 100755
--- a/runbot_builder/builder.py
+++ b/runbot_builder/builder.py
@@ -43,12 +43,12 @@ class RunbotClient():
                 sleep_time = self.env['runbot.repo']._scheduler_loop_turn(host)
                 host.last_end_loop = fields.Datetime.now()
                 self.env.cr.commit()
-                self.env.reset()
+                self.env.clear()
                 self.sleep(sleep_time)
             except Exception as e:
                 _logger.exception('Builder main loop failed with: %s', e)
                 self.env.cr.rollback()
-                self.env.reset()
+                self.env.clear()
                 self.sleep(10)
 
             if self.ask_interrupt.is_set():
diff --git a/runbot_cla/__manifest__.py b/runbot_cla/__manifest__.py
index d6054d5e..a7cbd624 100644
--- a/runbot_cla/__manifest__.py
+++ b/runbot_cla/__manifest__.py
@@ -2,7 +2,7 @@
     'name': 'Runbot CLA',
     'category': 'Website',
     'summary': 'Runbot CLA',
-    'version': '2.0',
+    'version': '2.1',
     'description': "Runbot CLA",
     'author': 'Odoo SA',
     'depends': ['runbot'],
diff --git a/runbot_cla/data/runbot_build_config_data.xml b/runbot_cla/data/runbot_build_config_data.xml
index 560c895b..89092a02 100644
--- a/runbot_cla/data/runbot_build_config_data.xml
+++ b/runbot_cla/data/runbot_build_config_data.xml
@@ -5,11 +5,4 @@
         <field name="job_type">cla_check</field>
         <field name="protected" eval="True"/>
     </record>
-
-    <record id="runbot.runbot_build_config_default" model="runbot.build.config">
-        <field name="step_order_ids" eval="[(0, 0, {'step_id': ref('runbot_build_config_step_check_cla')})]"/>
-    </record>
-    <record id="runbot.runbot_build_config_default_no_run" model="runbot.build.config">
-        <field name="step_order_ids" eval="[(0, 0, {'step_id': ref('runbot_build_config_step_check_cla')})]"/>
-    </record>
 </odoo>