def getBuilderNames(self, categories=None): if categories == None: return util.naturalSort(self.botmaster.builderNames) # don't let them break it l = [] # respect addition order for name in self.botmaster.builderNames: bldr = self.botmaster.builders[name] if bldr.config.category in categories: l.append(name) return util.naturalSort(l)
def getBuilderNames(self, tags=None, categories=None): if categories is not None: # Categories is deprecated; pretend they said "tags". tags = categories if tags is None: return util.naturalSort(self.botmaster.builderNames) # don't let them break it l = [] # respect addition order for name in self.botmaster.builderNames: bldr = self.getBuilder(name) if bldr.matchesAnyTag(tags): l.append(name) return util.naturalSort(l)
def getBuilderNamesByProject(self, projectName): l = [] for name in self.botmaster.builderNames: bldr = self.botmaster.builders[name] if projectName == bldr.config.project: l.append(name) return util.naturalSort(l)
def content(self, request, cxt): master = request.site.buildbot_service.master repositories = [] for cb in self.project.codebases: for key, value in cb.iteritems(): repositories.append(value['repository']) if not isinstance(value['branch'], types.ListType): value['branch'] = [value['branch']] if 'defaultbranch' not in value.keys(): value['defaultbranch'] = value['branch'] branches = yield master.db.state.getObjectState(repositories) if len(branches) > 0: for cb in self.project.codebases: for key, value in cb.iteritems(): if value['repository'] in branches.keys(): value['branch'] = util.naturalSort( branches[value['repository']]) cxt['codebases'] = self.project.codebases cxt['selectedproject'] = self.project.name template = request.site.buildbot_service.templates.get_template( "project.html") template.autoescape = True defer.returnValue(template.render(**cxt))
def body(self, req): s = self.getStatus(req) data = "" data += "<h1>Build Slaves</h1>\n" used_by_builder = {} for bname in s.getBuilderNames(): b = s.getBuilder(bname) for bs in b.getSlaves(): slavename = bs.getName() if slavename not in used_by_builder: used_by_builder[slavename] = [] used_by_builder[slavename].append(bname) data += "<ol>\n" for name in util.naturalSort(s.getSlaveNames()): slave = s.getSlave(name) slave_status = s.botmaster.slaves[name].slave_status isBusy = len(slave_status.getRunningBuilds()) data += " <li><a href=\"%s\">%s</a>:\n" % (req.childLink(urllib.quote(name,'')), name) data += " <ul>\n" version = slave.getVersion() data += "<li>Running Buildbot version: %s" % version builder_links = ['<a href="%s">%s</a>' % (req.childLink("../builders/%s" % bname),bname) for bname in used_by_builder.get(name, [])] if builder_links: data += (" <li>Used by Builders: %s</li>\n" % ", ".join(builder_links)) else: data += " <li>Not used by any Builders</li>\n" if slave.isConnected(): data += " <li>Slave is currently connected</li>\n" admin = slave.getAdmin() if admin: # munge it to avoid feeding the spambot harvesters admin = admin.replace("@", " -at- ") data += " <li>Admin: %s</li>\n" % admin last = slave.lastMessageReceived() if last: lt = time.strftime("%Y-%b-%d %H:%M:%S", time.localtime(last)) age = abbreviate_age(time.time() - last) data += " <li>Last heard from: %s " % age data += '<font size="-1">(%s)</font>' % lt data += "</li>\n" if isBusy: data += "<li>Slave is currently building.</li>" else: data += "<li>Slave is idle.</li>" else: data += " <li><b>Slave is NOT currently connected</b></li>\n" data += " </ul>\n" data += " </li>\n" data += "\n" data += "</ol>\n" return data
def content(self, request, cxt): status = self.getStatus(request) cxt['show_events_checked'] = request.args.get( "show_events", ["false"])[0].lower() == "true" cxt['branches'] = [b for b in request.args.get("branch", []) if b] cxt['failures_only'] = request.args.get("failures_only", ["false"])[0].lower() == "true" cxt['committers'] = [c for c in request.args.get("committer", []) if c] cxt['projects'] = [p for p in request.args.get("project", []) if p] # this has a set of toggle-buttons to let the user choose the # builders show_builders = request.args.get("show", []) show_builders.extend(request.args.get("builder", [])) cxt['show_builders'] = show_builders cxt['all_builders'] = status.getBuilderNames(tags=self.tags) # this has a set of toggle-buttons to let the user choose the # tags show_tags = request.args.get("tag", []) if not show_tags: show_tags = request.args.get("category", []) allBuilderNames = status.getBuilderNames() builders = [status.getBuilder(name) for name in allBuilderNames] allTags = set() for bldr in builders: tags = bldr.getTags() allTags.update(tags or []) allTags = util.naturalSort(list(allTags)) cxt['show_tags'] = show_tags cxt['all_tags'] = allTags # a couple of radio-button selectors for refresh time will appear # just after that text times = [ ("none", "None"), ("60", "60 seconds"), ("300", "5 minutes"), ("600", "10 minutes"), ] current_reload_time = request.args.get("reload", ["none"]) if current_reload_time: current_reload_time = current_reload_time[0] if not current_reload_time.isdigit(): current_reload_time = "none" if current_reload_time not in [t[0] for t in times]: times.insert(0, (current_reload_time, current_reload_time)) cxt['times'] = times cxt['current_reload_time'] = current_reload_time template = request.site.buildbot_service.templates.get_template( "waterfallhelp.html") return template.render(**cxt)
def getBuilderNames(self, categories=None): if categories == None: # assume this is already sorted... return self.botmaster.builderNames l = [] # respect addition order for name in self.botmaster.builderNames: bldr = self.botmaster.builders[name] if bldr.config.category in categories: l.append(name) return util.naturalSort(l)
def content(self, request, ctx): s = self.getStatus(request) # ?no_builders=1 disables build column show_builder_column = not (request.args.get('no_builders', '0')[0]) == '1' ctx['show_builder_column'] = show_builder_column used_by_builder = {} for bname in s.getBuilderNames(): b = s.getBuilder(bname) for bs in b.getSlaves(): slavename = bs.getName() if slavename not in used_by_builder: used_by_builder[slavename] = [] used_by_builder[slavename].append(bname) slaves = ctx['slaves'] = [] for name in util.naturalSort(s.getSlaveNames()): info = {} slaves.append(info) slave = s.getSlave(name) slave_status = s.botmaster.slaves[name].slave_status info['running_builds'] = len(slave_status.getRunningBuilds()) info['link'] = request.childLink(urllib.quote(name, '')) info['name'] = name if show_builder_column: info['builders'] = [] for b in used_by_builder.get(name, []): info['builders'].append( dict(link=request.childLink("../builders/%s" % b), name=b)) info['version'] = slave.getVersion() info['connected'] = slave.isConnected() info['connectCount'] = slave.getConnectCount() info['paused'] = slave.isPaused() info['admin'] = slave.getAdmin() or u'' last = slave.lastMessageReceived() if last: info['last_heard_from_age'] = abbreviate_age(time.time() - last) info['last_heard_from_time'] = time.strftime( "%Y-%b-%d %H:%M:%S", time.localtime(last)) template = request.site.buildbot_service.templates.get_template( "buildslaves.html") data = template.render(**ctx) return data
def getBuilderNames(self, categories=None, sort=False): l = [] if categories is not None: # respect addition order for name in self.botmaster.builderNames: bldr = self.botmaster.builders[name] if bldr.config.category in categories: l.append(name) else: l = self.botmaster.builderNames if sort: return util.naturalSort(l) return l
def content(self, request, ctx): s = self.getStatus(request) #?no_builders=1 disables build column show_builder_column = not (request.args.get('no_builders', '0')[0])=='1' ctx['show_builder_column'] = show_builder_column used_by_builder = {} for bname in s.getBuilderNames(): b = s.getBuilder(bname) for bs in b.getSlaves(): slavename = bs.getName() if slavename not in used_by_builder: used_by_builder[slavename] = [] used_by_builder[slavename].append(bname) slaves = ctx['slaves'] = [] for name in util.naturalSort(s.getSlaveNames()): info = {} slaves.append(info) slave = s.getSlave(name) slave_status = s.botmaster.slaves[name].slave_status info['running_builds'] = len(slave_status.getRunningBuilds()) info['link'] = request.childLink(urllib.quote(name,'')) info['name'] = name if show_builder_column: info['builders'] = [] for b in used_by_builder.get(name, []): info['builders'].append(dict(link=request.childLink("../builders/%s" % b), name=b)) info['version'] = slave.getVersion() info['connected'] = slave.isConnected() info['connectCount'] = slave.getConnectCount() if slave.isConnected(): info['admin'] = unicode(slave.getAdmin() or '', 'utf-8') last = slave.lastMessageReceived() if last: info['last_heard_from_age'] = abbreviate_age(time.time() - last) info['last_heard_from_time'] = time.strftime("%Y-%b-%d %H:%M:%S", time.localtime(last)) template = request.site.buildbot_service.templates.get_template("buildslaves.html") data = template.render(**ctx) return data
def getBuilderNames(self, categories=None): if categories == None: #YOCTO PROJECT ADDITION import os import ast sortbuilders="" sortbuilders=ast.literal_eval(os.environ.get('YOCTO_SORTED_BUILDERS')) #I hate the way this is sorted. This is the laziest way to get this #in the format I want. return sortbuilders #YOCTO PROJECT ADDITION #return util.naturalSort(self.botmaster.builderNames) # don't let them break it l = [] # respect addition order for name in self.botmaster.builderNames: bldr = self.botmaster.builders[name] if bldr.config.category in categories: l.append(name) return util.naturalSort(l)
def content(self, request, ctx): s = self.getStatus(request) used_by_builder = {} for bname in s.getBuilderNames(): b = s.getBuilder(bname) for bs in b.getSlaves(): slavename = bs.getName() if slavename not in used_by_builder: used_by_builder[slavename] = [] used_by_builder[slavename].append(bname) slaves = ctx["slaves"] = [] for name in util.naturalSort(s.getSlaveNames()): info = {} slaves.append(info) slave = s.getSlave(name) slave_status = s.botmaster.slaves[name].slave_status info["running_builds"] = len(slave_status.getRunningBuilds()) info["link"] = request.childLink(urllib.quote(name, "")) info["name"] = name info["builders"] = [] for b in used_by_builder.get(name, []): info["builders"].append(dict(link=request.childLink("../builders/%s" % b), name=b)) info["version"] = slave.getVersion() info["connected"] = slave.isConnected() if slave.isConnected(): info["admin"] = unicode(slave.getAdmin(), "utf-8") last = slave.lastMessageReceived() if last: info["last_heard_from_age"] = abbreviate_age(time.time() - last) info["last_heard_from_time"] = time.strftime("%Y-%b-%d %H:%M:%S", time.localtime(last)) template = request.site.buildbot_service.templates.get_template("buildslaves.html") data = template.render(**ctx) return data
def content(self, request, ctx): s = self.getStatus(request) used_by_builder = {} for bname in s.getBuilderNames(): b = s.getBuilder(bname) for bs in b.getSlaves(): slavename = bs.getName() if slavename not in used_by_builder: used_by_builder[slavename] = [] used_by_builder[slavename].append(bname) slaves = ctx['slaves'] = [] for name in util.naturalSort(s.getSlaveNames()): info = {} slaves.append(info) slave = s.getSlave(name) slave_status = s.botmaster.slaves[name].slave_status info['running_builds'] = len(slave_status.getRunningBuilds()) info['link'] = request.childLink(urllib.quote(name,'')) info['name'] = name info['builders'] = [{'link': request.childLink("../builders/%s" % bname), 'name': bname}] info['version'] = slave.getVersion() info['connected'] = slave.isConnected() if slave.isConnected(): info['admin'] = slave.getAdmin() last = slave.lastMessageReceived() if last: info['last_heard_from_age'] = abbreviate_age(time.time() - last) info['last_heard_from_time'] = time.strftime("%Y-%b-%d %H:%M:%S", time.localtime(last)) template = request.site.buildbot_service.templates.get_template("buildslaves.html") data = template.render(**ctx) return data
def displayTags(self, builderList, debugInfo): """Display the top tags line.""" count = 0 for tag in builderList: count += len(builderList[tag]) tags = util.naturalSort(builderList.keys()) cs = [] for tag in tags: c = {} c["name"] = tag # To be able to align the table correctly, we need to know # what percentage of space this tag will be taking. This is # (#Builders in tag) / (#Builders Total) * 100. c["size"] = (len(builderList[tag]) * 100) / count cs.append(c) return cs
def content(self, request, cxt): """This method builds the regular grid display. That is, build stamps across the top, build hosts down the left side """ # get url parameters numBuilds = int(request.args.get("width", [5])[0]) tags = request.args.get("tag", []) if not tags: tags = request.args.get("category", []) branch = request.args.get("branch", [ANYBRANCH])[0] if branch == 'trunk': branch = None # and the data we want to render status = self.getStatus(request) stamps = self.getRecentSourcestamps(status, numBuilds, tags, branch) cxt['refresh'] = self.get_reload_time(request) cxt.update({ 'tags': tags, 'branch': branch, 'ANYBRANCH': ANYBRANCH, 'stamps': [map(SourceStamp.asDict, sstamp) for sstamp in stamps], }) sortedBuilderNames = util.naturalSort(status.getBuilderNames()) cxt['builders'] = [] for bn in sortedBuilderNames: builds = [None] * len(stamps) builder = status.getBuilder(bn) if tags and not builder.matchesAnyTag(tags): continue for build in self.getRecentBuilds(builder, numBuilds, branch): ss = build.getSourceStamps(absolute=True) key = self.getSourceStampKey(ss) for i, sstamp in enumerate(stamps): if key == self.getSourceStampKey( sstamp) and builds[i] is None: builds[i] = build b = yield self.builder_cxt(request, builder) b['builds'] = [] for build in builds: b['builds'].append(self.build_cxt(request, build)) cxt['builders'].append(b) self.clearRecentBuildsCache() template = request.site.buildbot_service.templates.get_template( "grid.html") defer.returnValue(template.render(**cxt))
def content(self, request, cxt): """This method builds the transposed grid display. That is, build hosts across the top, build stamps down the left side """ # get url parameters numBuilds = int(request.args.get("length", [5])[0]) tags = request.args.get("tag", []) if not tags: tags = request.args.get("category", []) branch = request.args.get("branch", [ANYBRANCH])[0] if branch == 'trunk': branch = None rev_order = request.args.get("rev_order", [self.default_rev_order])[0] if rev_order not in ["asc", "desc"]: rev_order = self.default_rev_order cxt['refresh'] = self.get_reload_time(request) # and the data we want to render status = self.getStatus(request) stamps = self.getRecentSourcestamps(status, numBuilds, tags, branch) cxt.update({ 'tags': tags, 'branch': branch, 'ANYBRANCH': ANYBRANCH, 'stamps': [map(SourceStamp.asDict, sstamp) for sstamp in stamps], }) sortedBuilderNames = util.naturalSort(status.getBuilderNames()) cxt['sorted_builder_names'] = sortedBuilderNames cxt['builder_builds'] = builder_builds = [] cxt['builders'] = builders = [] cxt['range'] = range(len(stamps)) if rev_order == "desc": cxt['range'].reverse() for bn in sortedBuilderNames: builds = [None] * len(stamps) builder = status.getBuilder(bn) if tags and not builder.matchesAnyTag(tags): continue for build in self.getRecentBuilds(builder, numBuilds, branch): # TODO: support multiple sourcestamps ss = build.getSourceStamps(absolute=True) key = self.getSourceStampKey(ss) for i, sstamp in enumerate(stamps): if key == self.getSourceStampKey( sstamp) and builds[i] is None: builds[i] = build b = yield self.builder_cxt(request, builder) builders.append(b) builder_builds.append( map(lambda b: self.build_cxt(request, b), builds)) self.clearRecentBuildsCache() template = request.site.buildbot_service.templates.get_template( 'grid_transposed.html') defer.returnValue(template.render(**cxt))
def test_alphanum(self): l1 = 'aa10ab aa1ab aa10aa f a aa3 aa30 aa3a aa30a'.split() l2 = 'a aa1ab aa3 aa3a aa10aa aa10ab aa30 aa30a f'.split() self.assertEqual(util.naturalSort(l1), l2)
def test_numeric(self): self.assertEqual( util.naturalSort(['1', '10', '11', '2', '20']), ['1', '2', '10', '11', '20'])
def test_alpha(self): self.assertEqual( util.naturalSort(['x', 'aa', 'ab']), ['aa', 'ab', 'x'])
def content(self, request, cxt): master = request.site.buildbot_service.master status = self.getStatus(request) comparison_info = { "builders0": { "codebases": {}, "output": [] }, "builders1": { "codebases": {}, "output": [] } } # Get codebases/branch info repositories = [] for cb in self.project.codebases: for key, value in cb.iteritems(): repositories.append(value['repository']) if not isinstance(value['branch'], types.ListType): value['branch'] = [value['branch']] if 'defaultbranch' not in value.keys(): value['defaultbranch'] = value['branch'] branches = yield master.db.state.getObjectState(repositories) if len(branches) > 0: for cb in self.project.codebases: for key, value in cb.iteritems(): # Set the default branches on each of the codebases comparison_info["builders0"]["codebases"][key] = \ comparison_info["builders1"]["codebases"][key] = value['defaultbranch'] if value['repository'] in branches.keys(): value['branch'] = util.naturalSort( branches[value['repository']]) def set_instant_json(json_name, cmp_info): # Create our instant json for the given builders for bName, branch in cmp_info["codebases"].iteritems(): request.args[bName + "_branch"] = branch url = status.getBuildbotURL() + path_to_json_builders( request, self.project.name) # Remove the extra array we don't need for autobahn sources = {} for n, src in cmp_info["codebases"].iteritems(): sources[n] = src[0] filters = {"project": self.project.name, "sources": sources} cxt['instant_json'][json_name] = \ { "url": url, "data": json.dumps(cmp_info["output"], separators=(',', ':')), "waitForPush": status.master.config.autobahn_push, "pushFilters": { "buildFinished": filters, } } # Remove from the object as we no longer need these values del cmp_info["output"] # Get build data for each branch for name, obj in comparison_info.iteritems(): # Set branches based on url args args = request.args.get(name, None) if args is not None: split = urllib.unquote(args[0]).split("&") for s in split: branch_info = s.split("=") if len(branch_info) == 2: branch_name = branch_info[0].replace("_branch", "") obj["codebases"][branch_name] = [branch_info[1]] # Create a copy of the request to pass around with the updated codebases info for bName, branch in obj["codebases"].iteritems(): request.args[bName + "_branch"] = branch # Create our json j = SingleProjectJsonResource(status, self.project) builds = yield j.asDict(request) obj["output"] = builds["builders"] # Set the instant json for each of the comparison branches for name, obj in comparison_info.iteritems(): set_instant_json(name, obj) # Remove the extra array in our codebases tmp_codebases = {} for c in self.project.codebases: for name, cb in c.iteritems(): tmp_codebases[name] = cb codebases_json = { "codebases": tmp_codebases, "defaults": comparison_info, "url": path_to_comparison(request, self.project.name) } cxt['instant_json']['codebases'] = { "data": json.dumps(codebases_json, separators=(',', ':')) } cxt['path_to_codebases'] = path_to_codebases(request, self.project.name) cxt['selectedproject'] = self.project.name template = request.site.buildbot_service.templates.get_template( "branch_comparison.html") template.autoescape = True defer.returnValue(template.render(**cxt))
def content_with_db_data(self, changes, brcounts, request, ctx): status = self.getStatus(request) ctx['refresh'] = self.get_reload_time(request) # we start with all Builders available to this Waterfall: this is # limited by the config-file -time tags= argument, and defaults # to all defined Builders. allBuilderNames = status.getBuilderNames(tags=self.tags) builders = [status.getBuilder(name) for name in allBuilderNames] # but if the URL has one or more builder= arguments (or the old show= # argument, which is still accepted for backwards compatibility), we # use that set of builders instead. We still don't show anything # outside the config-file time set limited by tags=. showBuilders = request.args.get("show", []) showBuilders.extend(request.args.get("builder", [])) if showBuilders: builders = [b for b in builders if b.name in showBuilders] # now, if the URL has one or category= arguments, use them as a # filter: only show those builders which belong to one of the given # tags. showTags = request.args.get("tag", []) if not showTags: showTags = request.args.get("category", []) if showTags: builders = [b for b in builders if b.matchesAnyTag(showTags)] # If the URL has the failures_only=true argument, we remove all the # builders that are not currently red or won't be turning red at the end # of their current run. failuresOnly = request.args.get("failures_only", ["false"])[0] if failuresOnly.lower() == "true": builders = [b for b in builders if not self.isSuccess(b)] (changeNames, builderNames, timestamps, eventGrid, sourceEvents) = \ self.buildGrid(request, builders, changes) # start the table: top-header material locale_enc = locale.getdefaultlocale()[1] if locale_enc is not None: locale_tz = unicode(time.tzname[time.localtime()[-1]], locale_enc) else: locale_tz = unicode(time.tzname[time.localtime()[-1]]) ctx['tz'] = locale_tz ctx['changes_url'] = request.childLink("../changes") bn = ctx['builders'] = [] for name in builderNames: builder = status.getBuilder(name) top_box = ITopBox(builder).getBox(request) current_box = ICurrentBox(builder).getBox(status, brcounts) bn.append({ 'name': name, 'url': request.childLink("../builders/%s" % urllib.quote(name, safe='')), 'top': top_box.text, 'top_class': top_box.class_, 'status': current_box.text, 'status_class': current_box.class_, }) ctx.update( self.phase2(request, changeNames + builderNames, timestamps, eventGrid, sourceEvents)) def with_args(req, remove_args=[], new_args=[], new_path=None): # sigh, nevow makes this sort of manipulation easier newargs = req.args.copy() for argname in remove_args: newargs[argname] = [] if "branch" in newargs: newargs["branch"] = [b for b in newargs["branch"] if b] for k, v in new_args: if k in newargs: newargs[k].append(v) else: newargs[k] = [v] newquery = "&".join([ "%s=%s" % (urllib.quote(k), urllib.quote(v)) for k in newargs for v in newargs[k] ]) if new_path: new_url = new_path elif req.prepath: new_url = req.prepath[-1] else: new_url = '' if newquery: new_url += "?" + newquery return new_url if timestamps: bottom = timestamps[-1] ctx['nextpage'] = with_args(request, ["last_time"], [("last_time", str(int(bottom)))]) helpurl = path_to_root(request) + "waterfall/help" ctx['help_url'] = with_args(request, new_path=helpurl) if self.get_reload_time(request) is not None: ctx['no_reload_page'] = with_args(request, remove_args=["reload"]) # get alphabetically sorted list of all tags tags = set() builderNames = status.getBuilderNames() for builderName in builderNames: builder = status.getBuilder(builderName) tags.update(builder.getTags() or []) tags = util.naturalSort(list(tags)) ctx['tags'] = tags template = request.site.buildbot_service.templates.get_template( "waterfall.html") data = template.render(**ctx) return data
def test_alpha(self): self.assertEqual(util.naturalSort(["x", "aa", "ab"]), ["aa", "ab", "x"])
def test_numeric(self): self.assertEqual(util.naturalSort(['1', '10', '11', '2', '20']), ['1', '2', '10', '11', '20'])
def test_alpha(self): self.assertEqual(util.naturalSort(['x', 'aa', 'ab']), ['aa', 'ab', 'x'])
def test_numeric(self): self.assertEqual(util.naturalSort(["1", "10", "11", "2", "20"]), ["1", "2", "10", "11", "20"])
def body(self, req): s = self.getStatus(req) data = "" data += "<h1>Build Slaves</h1>\n" used_by_builder = {} for bname in s.getBuilderNames(): b = s.getBuilder(bname) for bs in b.getSlaves(): slavename = bs.getName() if slavename not in used_by_builder: used_by_builder[slavename] = [] used_by_builder[slavename].append(bname) data += "<ol>\n" for name in util.naturalSort(s.getSlaveNames()): slave = s.getSlave(name) slave_status = s.botmaster.slaves[name].slave_status isBusy = len(slave_status.getRunningBuilds()) data += " <li><a href=\"%s\">%s</a>:\n" % (req.childLink( urllib.quote(name, '')), name) data += " <ul>\n" version = slave.getVersion() data += "<li>Running Buildbot version: %s" % version builder_links = [ '<a href="%s">%s</a>' % (req.childLink("../builders/%s" % bname), bname) for bname in used_by_builder.get(name, []) ] if builder_links: data += (" <li>Used by Builders: %s</li>\n" % ", ".join(builder_links)) else: data += " <li>Not used by any Builders</li>\n" if slave.isConnected(): data += " <li>Slave is currently connected</li>\n" admin = slave.getAdmin() if admin: # munge it to avoid feeding the spambot harvesters admin = admin.replace("@", " -at- ") data += " <li>Admin: %s</li>\n" % admin last = slave.lastMessageReceived() if last: lt = time.strftime("%Y-%b-%d %H:%M:%S", time.localtime(last)) age = abbreviate_age(time.time() - last) data += " <li>Last heard from: %s " % age data += '<font size="-1">(%s)</font>' % lt data += "</li>\n" if isBusy: data += "<li>Slave is currently building.</li>" else: data += "<li>Slave is idle.</li>" else: data += " <li><b>Slave is NOT currently connected</b></li>\n" data += " </ul>\n" data += " </li>\n" data += "\n" data += "</ol>\n" return data