Exemple #1
0
    def body(self, req):
        status = self.getStatus(req)
        control = self.getControl(req)

        builders = req.args.get("builder", status.getBuilderNames())
        branches = [b for b in req.args.get("branch", []) if b]

        data = ""

        data += "<h2>Latest builds: %s</h2>\n" % ", ".join(branches)
        data += "<table>\n"

        building = False
        online = 0
        base_builders_url = self.path_to_root(req) + "builders/"
        for bn in builders:
            base_builder_url = base_builders_url + urllib.quote(bn, safe='')
            builder = status.getBuilder(bn)
            data += "<tr>\n"
            data += '<td class="box"><a href="%s">%s</a></td>\n' \
                  % (base_builder_url, html.escape(bn))
            builds = list(builder.generateFinishedBuilds(map_branches(branches),
                                                         num_builds=1))
            if builds:
                b = builds[0]
                url = (base_builder_url + "/builds/%d" % b.getNumber())
                try:
                    label = b.getProperty("got_revision")
                except KeyError:
                    label = None
                if not label or len(str(label)) > 20:
                    label = "#%d" % b.getNumber()
                text = ['<a href="%s">%s</a>' % (url, label)]
                text.extend(b.getText())
                box = Box(text, b.getColor(),
                          class_="LastBuild box %s" % build_get_class(b))
                data += box.td(align="center")
            else:
                data += '<td class="LastBuild box" >no build</td>\n'
            current_box = ICurrentBox(builder).getBox(status)
            data += current_box.td(align="center")

            builder_status = builder.getState()[0]
            if builder_status == "building":
                building = True
                online += 1
            elif builder_status != "offline":
                online += 1

        data += "</table>\n"

        if control is not None:
            if building:
                stopURL = "builders/_all/stop"
                data += make_stop_form(stopURL, True, "Builds")
            if online:
                forceURL = "builders/_all/force"
                data += make_force_build_form(forceURL, True)

        return data
Exemple #2
0
 def getBox(self, req):
     assert interfaces.IBuilderStatus(self.original)
     branches = [b for b in req.args.get("branch", []) if b]
     builder = self.original
     builds = list(builder.generateFinishedBuilds(map_branches(branches),
                                                  num_builds=1))
     if not builds:
         return Box(["none"], class_="LastBuild")
     b = builds[0]
     url = path_to_build(req, b)
     text = b.getText()
     tests_failed = b.getSummaryStatistic('tests-failed', operator.add, 0)
     if tests_failed: text.extend(["Failed tests: %d" % tests_failed])
     # TODO: maybe add logs?
     class_ = build_get_class(b)
     return Box(text, urlbase=url, class_="LastBuild %s" % class_)
    def getBox(self, req):
        urlbase = path_to_step(req, self.original)
        text = self.original.getText()
        if text is None:
            log.msg("getText() gave None", urlbase)
            text = []
        text = text[:]

        def native_link_fn(l):
            if l.hasContents():
                url = urlbase + "/logs/%s" % urllib.quote(l.getName())
            else:
                url = None
            return url

        logs, urls, _ = getStepLogsURLsAndAliases(
            self.original,
            False,
            native_link_fn,
        )

        cxt = dict(text=text, logs=logs, urls=urls)

        template = req.site.buildbot_service.templates.get_template(
            "box_macros.html")
        text = template.module.step_box(**unicodify(cxt))

        class_ = "BuildStep " + build_get_class(self.original)
        return Box(text,
                   class_=class_,
                   buildNumber=self.original.build_number,
                   builder=self.original.builder.getName(),
                   stepName=self.original.getName())
 def getBox(self, req):
     url = req.childLink("../changes/%d" % self.original.number)
     template = req.site.buildbot_service.templates.get_template("change_macros.html")
     text = template.module.box_contents(url=url,
                                         who=self.original.getShortAuthor(),
                                         pageTitle=self.original.comments)
     return Box([text], class_="Change")
 def getBox(self, req):
     text = self.original.getText()
     color = self.original.getColor()
     class_ = "Event"
     if color:
         class_ += " " + color
     return Box(text, color, class_=class_)
Exemple #6
0
    def getBox(self, req):
        urlbase = path_to_step(req, self.original)
        text = self.original.getText()
        if text is None:
            log.msg("getText() gave None", urlbase)
            text = []
        text = text[:]
        logs = self.original.getLogs()
        
        cxt = dict(text=text, logs=[], urls=[])

        for num in range(len(logs)):
            name = logs[num].getName()
            if logs[num].hasContents():
                url = urlbase + "/logs/%s" % urllib.quote(name)
            else:
                url = None
            cxt['logs'].append(dict(name=name, url=url))

        for name, target in self.original.getURLs().items():
            cxt['urls'].append(dict(link=target,name=name))

        template = req.site.buildbot_service.templates.get_template("box_macros.html")
        text = template.module.step_box(**cxt)
        
        class_ = "BuildStep " + build_get_class(self.original)
        return Box(text, class_=class_)
Exemple #7
0
 def getBox(self, req):
     assert interfaces.IBuilderStatus(self.original)
     branches = [b for b in req.args.get("branch", []) if b]
     builder = self.original
     builds = list(
         builder.generateFinishedBuilds(map_branches(branches),
                                        num_builds=1))
     if not builds:
         return Box(["none"], "white", class_="LastBuild")
     b = builds[0]
     name = b.getBuilder().getName()
     number = b.getNumber()
     url = path_to_build(req, b)
     text = b.getText()
     # TODO: maybe add logs?
     # TODO: add link to the per-build page at 'url'
     c = b.getColor()
     class_ = build_get_class(b)
     return Box(text, c, class_="LastBuild %s" % class_)
    def getBox(self, status, brcounts):
        # getState() returns offline, idle, or building
        state, builds = self.original.getState()

        # look for upcoming builds. We say the state is "waiting" if the
        # builder is otherwise idle and there is a scheduler which tells us a
        # build will be performed some time in the near future. TODO: this
        # functionality used to be in BuilderStatus.. maybe this code should
        # be merged back into it.
        upcoming = []
        builderName = self.original.getName()
        for s in status.getSchedulers():
            if builderName in s.listBuilderNames():
                upcoming.extend(s.getPendingBuildTimes())
        if state == "idle" and upcoming:
            state = "waiting"

        if state == "building":
            text = ["building"]
            if builds:
                for b in builds:
                    eta = b.getETA()
                    if len(builds) <= 2:
                        # Show more information if there's only one or two
                        # builds.
                        started = time.localtime(b.getTimes()[0])
                        text.extend(
                            ["start:",
                             time.strftime("%H:%M", started)])
                    text.extend(self.formatETA("ETA in", eta))
        elif state == "offline":
            text = ["offline"]
        elif state == "idle":
            text = ["idle"]
        elif state == "waiting":
            text = ["waiting"]
        else:
            # just in case I add a state and forget to update this
            text = [state]

        # TODO: for now, this pending/upcoming stuff is in the "current
        # activity" box, but really it should go into a "next activity" row
        # instead. The only times it should show up in "current activity" is
        # when the builder is otherwise idle.

        # are any builds pending? (waiting for a slave to be free)
        brcount = brcounts[builderName]
        if brcount:
            text.append("%d pending" % brcount)
        for t in upcoming:
            if t is not None:
                eta = t - util.now()
                text.extend(self.formatETA("next in", eta))
        return Box(text, class_="Activity " + state)
Exemple #9
0
 def getBox(self, req):
     b = self.original
     number = b.getNumber()
     url = path_to_build(req, b)
     reason = b.getReason()
     template = req.site.buildbot_service.templates.get_template("box_macros.html")
     text = template.module.build_box(reason=reason,url=url,number=number)
     class_ = "start"
     if b.isFinished() and not b.getSteps():
         # the steps have been pruned, so there won't be any indication
         # of whether it succeeded or failed.
         class_ = build_get_class(b)
     return Box([text], class_="BuildStep " + class_)
Exemple #10
0
 def getBox(self, req):
     b = self.original
     number = b.getNumber()
     url = path_to_build(req, b)
     reason = b.getReason()
     text = ('<a title="Reason: %s" href="%s">Build %d</a>'
             % (html.escape(reason), url, number))
     class_ = "start"
     if b.isFinished() and not b.getSteps():
         # the steps have been pruned, so there won't be any indication
         # of whether it succeeded or failed.
         class_ = build_get_class(b)
     return Box([text], class_="BuildStep " + class_)
    def getBox(self, status):
        # getState() returns offline, idle, or building
        state, builds = self.original.getState()

        # look for upcoming builds. We say the state is "waiting" if the
        # builder is otherwise idle and there is a scheduler which tells us a
        # build will be performed some time in the near future. TODO: this
        # functionality used to be in BuilderStatus.. maybe this code should
        # be merged back into it.
        upcoming = []
        builderName = self.original.getName()
        for s in status.getSchedulers():
            if builderName in s.listBuilderNames():
                upcoming.extend(s.getPendingBuildTimes())
        if state == "idle" and upcoming:
            state = "waiting"

        if state == "building":
            color = "yellow"
            text = ["building"]
            if builds:
                for b in builds:
                    eta = b.getETA()
                    text.extend(self.formatETA("ETA in", eta))
        elif state == "offline":
            color = "red"
            text = ["offline"]
        elif state == "idle":
            color = "white"
            text = ["idle"]
        elif state == "waiting":
            color = "yellow"
            text = ["waiting"]
        else:
            # just in case I add a state and forget to update this
            color = "white"
            text = [state]

        # TODO: for now, this pending/upcoming stuff is in the "current
        # activity" box, but really it should go into a "next activity" row
        # instead. The only times it should show up in "current activity" is
        # when the builder is otherwise idle.

        # are any builds pending? (waiting for a slave to be free)
        pbs = self.original.getPendingBuilds()
        if pbs:
            text.append("%d pending" % len(pbs))
        for t in upcoming:
            eta = t - util.now()
            text.extend(self.formatETA("next in", eta))
        return Box(text, color=color, class_="Activity " + state)
Exemple #12
0
 def getBox(self, req):
     urlbase = path_to_step(req, self.original)
     text = self.original.getText()
     if text is None:
         log.msg("getText() gave None", urlbase)
         text = []
     text = text[:]
     logs = self.original.getLogs()
     for num in range(len(logs)):
         name = logs[num].getName()
         if logs[num].hasContents():
             url = urlbase + "/logs/%s" % urllib.quote(name)
             text.append("<a href=\"%s\">%s</a>" % (url, html.escape(name)))
         else:
             text.append(html.escape(name))
     urls = self.original.getURLs()
     ex_url_class = "BuildStep external"
     for name, target in urls.items():
         text.append('[<a href="%s" class="%s">%s</a>]' %
                     (target, ex_url_class, html.escape(name)))
     class_ = "BuildStep " + build_get_class(self.original)
     return Box(text, class_=class_)
Exemple #13
0
    def getBox(self, req):
        b = self.original
        number = b.getNumber()
        url = path_to_build(req, b)
        reason = b.getReason()

        # Update the buildbot BuildStatus so that it displays the hg revision number as the build number
        changedesc = '(%d)' % number
        revision = b.getSourceStamp().revision
        if (len(b.changes) > 0):
            changedesc = '%s (%d)' % (b.changes[0].revision, number)
        if (revision):
            changedesc = "%s (%d)" % (revision, number)
        text = ('<a title="Reason: %s" href="%s">Build %s</a>' %
                (html.escape(reason), url, changedesc))

        class_ = "start"
        if b.isFinished() and not b.getSteps():
            # the steps have been pruned, so there won't be any indication
            # of whether it succeeded or failed.
            class_ = build_get_class(b)
        return Box([text], class_="BuildStep " + class_)
Exemple #14
0
    def body(self, req):
        status = self.getStatus(req)
        control = self.getControl(req)

        builders = req.args.get("builder", status.getBuilderNames())
        branches = [b for b in req.args.get("branch", []) if b]

        data = ""

        data += "<h2>Latest builds: %s</h2>\n" % ", ".join(branches)
        data += "<table>\n"

        building = False
        online = 0
        base_builders_url = self.path_to_root(req) + "builders/"
        for bn in builders:
            base_builder_url = base_builders_url + urllib.quote(bn, safe='')
            builder = status.getBuilder(bn)
            data += "<tr>\n"
            data += '<td class="box"><a href="%s">%s</a></td>\n' \
                  % (base_builder_url, html.escape(bn))
            builds = list(
                builder.generateFinishedBuilds(map_branches(branches),
                                               num_builds=1))
            if builds:
                b = builds[0]
                url = (base_builder_url + "/builds/%d" % b.getNumber())
                try:
                    label = b.getProperty("got_revision")
                except KeyError:
                    label = None
                if not label or len(str(label)) > 20:
                    label = "#%d" % b.getNumber()
                text = ['<a href="%s">%s</a>' % (url, label)]
                text.extend(b.getText())
                box = Box(text,
                          b.getColor(),
                          class_="LastBuild box %s" % build_get_class(b))
                data += box.td(align="center")
            else:
                data += '<td class="LastBuild box" >no build</td>\n'
            current_box = ICurrentBox(builder).getBox(status)
            data += current_box.td(align="center")

            builder_status = builder.getState()[0]
            if builder_status == "building":
                building = True
                online += 1
            elif builder_status != "offline":
                online += 1

        data += "</table>\n"

        if control is not None:
            if building:
                stopURL = "builders/_all/stop"
                data += make_stop_form(stopURL, True, "Builds")
            if online:
                forceURL = "builders/_all/force"
                data += make_force_build_form(forceURL, True)

        return data
Exemple #15
0
    def phase2(self, request, sourceNames, timestamps, eventGrid,
               sourceEvents):
        data = ""
        if not timestamps:
            return data
        # first pass: figure out the height of the chunks, populate grid
        grid = []
        for i in range(1 + len(sourceNames)):
            grid.append([])
        # grid is a list of columns, one for the timestamps, and one per
        # event source. Each column is exactly the same height. Each element
        # of the list is a single <td> box.
        lastDate = time.strftime("<b>%d %b %Y</b>", time.localtime(util.now()))
        for r in range(0, len(timestamps)):
            chunkstrip = eventGrid[r]
            # chunkstrip is a horizontal strip of event blocks. Each block
            # is a vertical list of events, all for the same source.
            assert (len(chunkstrip) == len(sourceNames))
            maxRows = reduce(lambda x, y: max(x, y),
                             map(lambda x: len(x), chunkstrip))
            for i in range(maxRows):
                if i != maxRows - 1:
                    grid[0].append(None)
                else:
                    # timestamp goes at the bottom of the chunk
                    stuff = []
                    # add the date at the beginning (if it is not the same as
                    # today's date), and each time it changes
                    todayday = time.strftime("<b>%a</b>",
                                             time.localtime(timestamps[r]))
                    today = time.strftime("<b>%d %b %Y</b>",
                                          time.localtime(timestamps[r]))
                    if today != lastDate:
                        stuff.append(todayday)
                        stuff.append(today)
                        lastDate = today
                    stuff.append(
                        time.strftime("%H:%M:%S",
                                      time.localtime(timestamps[r])))
                    grid[0].append(
                        Box(text=stuff,
                            class_="Time",
                            valign="bottom",
                            align="center"))

            # at this point the timestamp column has been populated with
            # maxRows boxes, most None but the last one has the time string
            for c in range(0, len(chunkstrip)):
                block = chunkstrip[c]
                assert (block != None)  # should be [] instead
                for i in range(maxRows - len(block)):
                    # fill top of chunk with blank space
                    grid[c + 1].append(None)
                for i in range(len(block)):
                    # so the events are bottom-justified
                    b = IBox(block[i]).getBox(request)
                    b.parms['valign'] = "top"
                    b.parms['align'] = "center"
                    grid[c + 1].append(b)
            # now all the other columns have maxRows new boxes too
        # populate the last row, if empty
        gridlen = len(grid[0])
        for i in range(len(grid)):
            strip = grid[i]
            assert (len(strip) == gridlen)
            if strip[-1] == None:
                if sourceEvents[i - 1]:
                    filler = IBox(sourceEvents[i - 1]).getBox(request)
                else:
                    # this can happen if you delete part of the build history
                    filler = Box(text=["?"], align="center")
                strip[-1] = filler
            strip[-1].parms['rowspan'] = 1
        # second pass: bubble the events upwards to un-occupied locations
        # Every square of the grid that has a None in it needs to have
        # something else take its place.
        noBubble = request.args.get("nobubble", ['0'])
        noBubble = int(noBubble[0])
        if not noBubble:
            for col in range(len(grid)):
                strip = grid[col]
                if col == 1:  # changes are handled differently
                    for i in range(2, len(strip) + 1):
                        # only merge empty boxes. Don't bubble commit boxes.
                        if strip[-i] == None:
                            next = strip[-i + 1]
                            assert (next)
                            if next:
                                #if not next.event:
                                if next.spacer:
                                    # bubble the empty box up
                                    strip[-i] = next
                                    strip[-i].parms['rowspan'] += 1
                                    strip[-i + 1] = None
                                else:
                                    # we are above a commit box. Leave it
                                    # be, and turn the current box into an
                                    # empty one
                                    strip[-i] = Box([],
                                                    rowspan=1,
                                                    comment="commit bubble")
                                    strip[-i].spacer = True
                            else:
                                # we are above another empty box, which
                                # somehow wasn't already converted.
                                # Shouldn't happen
                                pass
                else:
                    for i in range(2, len(strip) + 1):
                        # strip[-i] will go from next-to-last back to first
                        if strip[-i] == None:
                            # bubble previous item up
                            assert (strip[-i + 1] != None)
                            strip[-i] = strip[-i + 1]
                            strip[-i].parms['rowspan'] += 1
                            strip[-i + 1] = None
                        else:
                            strip[-i].parms['rowspan'] = 1
        # third pass: render the HTML table
        for i in range(gridlen):
            data += " <tr>\n"
            for strip in grid:
                b = strip[i]
                if b:
                    data += b.td()
                else:
                    if noBubble:
                        data += td([])
                # Nones are left empty, rowspan should make it all fit
            data += " </tr>\n"
        return data
Exemple #16
0
 def getBox(self, req):
     #b = Box(["spacer"], "white")
     b = Box([])
     b.spacer = True
     return b
Exemple #17
0
 def getBox(self, req):
     text = self.original.getText()
     class_ = "Event"
     return Box(text, class_=class_)
Exemple #18
0
    def phase2(self, request, sourceNames, timestamps, eventGrid,
               sourceEvents):
        data = ""
        if not timestamps:
            return data
        # first pass: figure out the height of the chunks, populate grid
        grid = []
        for i in range(1+len(sourceNames)):
            grid.append([])
        # grid is a list of columns, one for the timestamps, and one per
        # event source. Each column is exactly the same height. Each element
        # of the list is a single <td> box.
        lastDate = time.strftime("<b>%d %b %Y</b>",
                                 time.localtime(util.now()))
        for r in range(0, len(timestamps)):
            chunkstrip = eventGrid[r]
            # chunkstrip is a horizontal strip of event blocks. Each block
            # is a vertical list of events, all for the same source.
            assert(len(chunkstrip) == len(sourceNames))
            maxRows = reduce(lambda x,y: max(x,y),
                             map(lambda x: len(x), chunkstrip))
            for i in range(maxRows):
                if i != maxRows-1:
                    grid[0].append(None)
                else:
                    # timestamp goes at the bottom of the chunk
                    stuff = []
                    # add the date at the beginning (if it is not the same as
                    # today's date), and each time it changes
                    todayday = time.strftime("<b>%a</b>",
                                             time.localtime(timestamps[r]))
                    today = time.strftime("<b>%d %b %Y</b>",
                                          time.localtime(timestamps[r]))
                    if today != lastDate:
                        stuff.append(todayday)
                        stuff.append(today)
                        lastDate = today
                    stuff.append(
                        time.strftime("%H:%M:%S",
                                      time.localtime(timestamps[r])))
                    grid[0].append(Box(text=stuff, class_="Time",
                                       valign="bottom", align="center"))

            # at this point the timestamp column has been populated with
            # maxRows boxes, most None but the last one has the time string
            for c in range(0, len(chunkstrip)):
                block = chunkstrip[c]
                assert(block != None) # should be [] instead
                for i in range(maxRows - len(block)):
                    # fill top of chunk with blank space
                    grid[c+1].append(None)
                for i in range(len(block)):
                    # so the events are bottom-justified
                    b = IBox(block[i]).getBox(request)
                    b.parms['valign'] = "top"
                    b.parms['align'] = "center"
                    grid[c+1].append(b)
            # now all the other columns have maxRows new boxes too
        # populate the last row, if empty
        gridlen = len(grid[0])
        for i in range(len(grid)):
            strip = grid[i]
            assert(len(strip) == gridlen)
            if strip[-1] == None:
                if sourceEvents[i-1]:
                    filler = IBox(sourceEvents[i-1]).getBox(request)
                else:
                    # this can happen if you delete part of the build history
                    filler = Box(text=["?"], align="center")
                strip[-1] = filler
            strip[-1].parms['rowspan'] = 1
        # second pass: bubble the events upwards to un-occupied locations
        # Every square of the grid that has a None in it needs to have
        # something else take its place.
        noBubble = request.args.get("nobubble",['0'])
        noBubble = int(noBubble[0])
        if not noBubble:
            for col in range(len(grid)):
                strip = grid[col]
                if col == 1: # changes are handled differently
                    for i in range(2, len(strip)+1):
                        # only merge empty boxes. Don't bubble commit boxes.
                        if strip[-i] == None:
                            next = strip[-i+1]
                            assert(next)
                            if next:
                                #if not next.event:
                                if next.spacer:
                                    # bubble the empty box up
                                    strip[-i] = next
                                    strip[-i].parms['rowspan'] += 1
                                    strip[-i+1] = None
                                else:
                                    # we are above a commit box. Leave it
                                    # be, and turn the current box into an
                                    # empty one
                                    strip[-i] = Box([], rowspan=1,
                                                    comment="commit bubble")
                                    strip[-i].spacer = True
                            else:
                                # we are above another empty box, which
                                # somehow wasn't already converted.
                                # Shouldn't happen
                                pass
                else:
                    for i in range(2, len(strip)+1):
                        # strip[-i] will go from next-to-last back to first
                        if strip[-i] == None:
                            # bubble previous item up
                            assert(strip[-i+1] != None)
                            strip[-i] = strip[-i+1]
                            strip[-i].parms['rowspan'] += 1
                            strip[-i+1] = None
                        else:
                            strip[-i].parms['rowspan'] = 1
        # third pass: render the HTML table
        for i in range(gridlen):
            data += " <tr>\n";
            for strip in grid:
                b = strip[i]
                if b:
                    # convert data to a unicode string, whacking any non-ASCII characters it might contain
                    s = b.td()
                    if isinstance(s, unicode):
                        s = s.encode("utf-8", "replace")
                    data += s
                else:
                    if noBubble:
                        data += td([])
                # Nones are left empty, rowspan should make it all fit
            data += " </tr>\n"
        return data
Exemple #19
0
 def getBox(self, req):
     #b = Box(["spacer"], "white")
     b = Box([])
     b.spacer = True
     return b
Exemple #20
0
    def body(self, req):
        from twisted.web import html
        status = self.getStatus(req)
        control = self.getControl(req)

        builders = req.args.get("builder", status.getBuilderNames())
        branches = [b for b in req.args.get("branch", []) if b]

        building = False
        online = 0
        
        base_builders_url = self.path_to_root(req) + "builders/"
        all_builders = [html.escape(bn) for bn in builders]
        trunk_builders = [bn for bn in all_builders if bn.startswith('trunk')]
        stable_builders = [bn for bn in all_builders if bn.startswith('stable')]  
        trunk_builders_link = 'waterfall?builder='+'&amp;builder='.join(trunk_builders)
        stable_builders_link = 'waterfall?builder=' + '&amp;builder='.join(stable_builders)
        tr_b = False
        st_b = False
        data = ""
        data += "<table class='grid' id='latest_builds'>"
        for bn in all_builders:
            if (bn.startswith('stable')) and (not st_b):
                st_b = True
                data += "<table class='grid' id='stable_builds'>\n"
                data += "<tr class='grid-header'><td class='grid-cell'><span>Latest Stable</span></td><td class='grid-cell'><a href=%s>Stable Tests</a></td><td class='grid-cell'><a href='Changelog/5.0'>Changelog</a></td></tr><\n>"[:-3]%(stable_builders_link)
            if (bn.startswith('trunk')) and (not tr_b):
                tr_b = True
                data += "<tr id='trunk_builds'><td colspan='3'></td>"
                data += "<tr class='grid-header'><td class='grid-cell'><span>Latest Trunk</span></td><td class='grid-cell'><a href='%s'>Trunk Tests</a></td><td class='grid-cell'><a href='Changelog/trunk'>Changelog</a></td></tr><\n>"[:-3]%(trunk_builders_link)
                 
            base_builder_url = base_builders_url + urllib.quote(bn, safe='')
            builder = status.getBuilder(bn)
            data += "<tr class='grid-row'>\n"
            data += '<td class="grid-cell"><a href="%s">%s</a></td>\n' \
                  % (base_builder_url, html.escape(bn))
            builds = list(builder.generateFinishedBuilds(map_branches(branches),
                                                         num_builds=1))
            if builds:
                b = builds[0]
                url = (base_builder_url + "/builds/%d" % b.getNumber())
                try:
                    label = b.getProperty("got_revision")
                except KeyError:
                    label = None
                if not label or len(str(label)) > 20:
                    label = "#%d" % b.getNumber()
                text = ['<a href="%s">%s</a>' % (url, label)]
                text.append(' '.join(b.getText()))
                box = Box(text, b.getColor(),
                        class_="LastBuild box %s" % build_get_class(b))
                data += box.td(class_="grid-cell",align="center")
            else:
                data += '<td class="grid-cell" align="center">no build</td>\n'
            current_box = ICurrentBox(builder).getBox(status)            
            data += current_box.td(class_="grid-cell",align="center")
            data+='</tr>'
            
            builder_status = builder.getState()[0]
            if builder_status == "building":
                building = True
                online += 1
            elif builder_status != "offline":
                online += 1

        data += "</table></table>\n"        
        if control is not None:
            if building:
                stopURL = "builders/_all/stop"
                data += make_stop_form(stopURL, True, "Builds")
            if online:
                forceURL = "builders/_all/force"
                data += make_force_build_form(forceURL, True)
        return data
 def getBox(self, req):
     url = req.childLink("../changes/%d" % self.original.number)
     text = self.original.get_HTML_box(url)
     return Box([text], color="white", class_="Change")