Package buildbot :: Package status :: Package web :: Module baseweb
[hide private]
[frames] | no frames]

Source Code for Module buildbot.status.web.baseweb

  1   
  2  import os, sys, urllib, weakref 
  3  from itertools import count 
  4   
  5  from zope.interface import implements 
  6  from twisted.python import log 
  7  from twisted.application import strports, service 
  8  from twisted.web import server, distrib, static, html 
  9  from twisted.spread import pb 
 10   
 11  from buildbot.interfaces import IControl, IStatusReceiver 
 12   
 13  from buildbot.status.web.base import HtmlResource, Box, \ 
 14       build_get_class, ICurrentBox, OneLineMixin, map_branches, \ 
 15       make_stop_form, make_force_build_form 
 16  from buildbot.status.web.feeds import Rss20StatusResource, \ 
 17       Atom10StatusResource 
 18  from buildbot.status.web.waterfall import WaterfallStatusResource 
 19  from buildbot.status.web.grid import GridStatusResource, TransposedGridStatusResource 
 20  from buildbot.status.web.changes import ChangesResource 
 21  from buildbot.status.web.builder import BuildersResource 
 22  from buildbot.status.web.buildstatus import BuildStatusStatusResource  
 23  from buildbot.status.web.slaves import BuildSlavesResource 
 24  from buildbot.status.web.xmlrpc import XMLRPCServer 
 25  from buildbot.status.web.about import AboutBuildbot 
 26  from buildbot.status.web.auth import IAuth, AuthFailResource 
 27   
 28  # this class contains the status services (WebStatus and the older Waterfall) 
 29  # which can be put in c['status']. It also contains some of the resources 
 30  # that are attached to the WebStatus at various well-known URLs, which the 
 31  # admin might wish to attach (using WebStatus.putChild) at other URLs. 
 32   
 33   
34 -class LastBuild(HtmlResource):
35 - def body(self, request):
36 return "missing\n"
37
38 -def getLastNBuilds(status, numbuilds, builders=[], branches=[]):
39 """Return a list with the last few Builds, sorted by start time. 40 builder_names=None means all builders 41 """ 42 43 # TODO: this unsorts the list of builder names, ick 44 builder_names = set(status.getBuilderNames()) 45 if builders: 46 builder_names = builder_names.intersection(set(builders)) 47 48 # to make sure that we get everything, we must get 'numbuilds' builds 49 # from *each* source, then sort by ending time, then trim to the last 50 # 20. We could be more efficient, but it would require the same 51 # gnarly code that the Waterfall uses to generate one event at a 52 # time. TODO: factor that code out into some useful class. 53 events = [] 54 for builder_name in builder_names: 55 builder = status.getBuilder(builder_name) 56 for build_number in count(1): 57 if build_number > numbuilds: 58 break # enough from this builder, move on to another 59 build = builder.getBuild(-build_number) 60 if not build: 61 break # no more builds here, move on to the next builder 62 #if not build.isFinished(): 63 # continue 64 (build_start, build_end) = build.getTimes() 65 event = (build_start, builder_name, build) 66 events.append(event) 67 def _sorter(a, b): 68 return cmp( a[:2], b[:2] )
69 events.sort(_sorter) 70 # now only return the actual build, and only return some of them 71 return [e[2] for e in events[-numbuilds:]] 72 73 74 # /one_line_per_build 75 # accepts builder=, branch=, numbuilds=, reload=
76 -class OneLinePerBuild(HtmlResource, OneLineMixin):
77 """This shows one line per build, combining all builders together. Useful 78 query arguments: 79 80 numbuilds=: how many lines to display 81 builder=: show only builds for this builder. Multiple builder= arguments 82 can be used to see builds from any builder in the set. 83 reload=: reload the page after this many seconds 84 """ 85 86 title = "Recent Builds" 87
88 - def __init__(self, numbuilds=20):
89 HtmlResource.__init__(self) 90 self.numbuilds = numbuilds
91
92 - def getChild(self, path, req):
93 status = self.getStatus(req) 94 builder = status.getBuilder(path) 95 return OneLinePerBuildOneBuilder(builder, numbuilds=self.numbuilds)
96
97 - def get_reload_time(self, request):
98 if "reload" in request.args: 99 try: 100 reload_time = int(request.args["reload"][0]) 101 return max(reload_time, 15) 102 except ValueError: 103 pass 104 return None
105
106 - def head(self, request):
107 head = '' 108 reload_time = self.get_reload_time(request) 109 if reload_time is not None: 110 head += '<meta http-equiv="refresh" content="%d">\n' % reload_time 111 return head
112
113 - def body(self, req):
114 status = self.getStatus(req) 115 control = self.getControl(req) 116 numbuilds = int(req.args.get("numbuilds", [self.numbuilds])[0]) 117 builders = req.args.get("builder", []) 118 branches = [b for b in req.args.get("branch", []) if b] 119 120 g = status.generateFinishedBuilds(builders, map_branches(branches), 121 numbuilds, max_search=numbuilds) 122 123 data = "" 124 125 # really this is "up to %d builds" 126 data += "<h1>Last %d finished builds: %s</h1>\n" % \ 127 (numbuilds, ", ".join(branches)) 128 if builders: 129 data += ("<p>of builders: %s</p>\n" % (", ".join(builders))) 130 data += "<ul>\n" 131 got = 0 132 building = False 133 online = 0 134 for build in g: 135 got += 1 136 data += " <li>" + self.make_line(req, build) + "</li>\n" 137 builder_status = build.getBuilder().getState()[0] 138 if builder_status == "building": 139 building = True 140 online += 1 141 elif builder_status != "offline": 142 online += 1 143 if not got: 144 data += " <li>No matching builds found</li>\n" 145 data += "</ul>\n" 146 147 if control is not None: 148 if building: 149 stopURL = "builders/_all/stop" 150 data += make_stop_form(stopURL, self.isUsingUserPasswd(req), 151 True, "Builds") 152 if online: 153 forceURL = "builders/_all/force" 154 data += make_force_build_form(forceURL, 155 self.isUsingUserPasswd(req), True) 156 157 return data
158 159 160 161 # /one_line_per_build/$BUILDERNAME 162 # accepts branch=, numbuilds= 163
164 -class OneLinePerBuildOneBuilder(HtmlResource, OneLineMixin):
165 - def __init__(self, builder, numbuilds=20):
166 HtmlResource.__init__(self) 167 self.builder = builder 168 self.builder_name = builder.getName() 169 self.numbuilds = numbuilds 170 self.title = "Recent Builds of %s" % self.builder_name
171
172 - def body(self, req):
173 status = self.getStatus(req) 174 numbuilds = int(req.args.get("numbuilds", [self.numbuilds])[0]) 175 branches = [b for b in req.args.get("branch", []) if b] 176 177 # walk backwards through all builds of a single builder 178 g = self.builder.generateFinishedBuilds(map_branches(branches), 179 numbuilds) 180 181 data = "" 182 data += ("<h1>Last %d builds of builder %s: %s</h1>\n" % 183 (numbuilds, self.builder_name, ", ".join(branches))) 184 data += "<ul>\n" 185 got = 0 186 for build in g: 187 got += 1 188 data += " <li>" + self.make_line(req, build) + "</li>\n" 189 if not got: 190 data += " <li>No matching builds found</li>\n" 191 data += "</ul>\n" 192 193 return data
194 195 # /one_box_per_builder 196 # accepts builder=, branch=
197 -class OneBoxPerBuilder(HtmlResource):
198 """This shows a narrow table with one row per builder. The leftmost column 199 contains the builder name. The next column contains the results of the 200 most recent build. The right-hand column shows the builder's current 201 activity. 202 203 builder=: show only builds for this builder. Multiple builder= arguments 204 can be used to see builds from any builder in the set. 205 """ 206 207 title = "Latest Build" 208
209 - def body(self, req):
210 status = self.getStatus(req) 211 control = self.getControl(req) 212 213 builders = req.args.get("builder", status.getBuilderNames()) 214 branches = [b for b in req.args.get("branch", []) if b] 215 216 data = "" 217 218 data += "<h2>Latest builds: %s</h2>\n" % ", ".join(branches) 219 data += "<table>\n" 220 221 building = False 222 online = 0 223 base_builders_url = self.path_to_root(req) + "builders/" 224 for bn in builders: 225 base_builder_url = base_builders_url + urllib.quote(bn, safe='') 226 builder = status.getBuilder(bn) 227 data += "<tr>\n" 228 data += '<td class="box"><a href="%s">%s</a></td>\n' \ 229 % (base_builder_url, html.escape(bn)) 230 builds = list(builder.generateFinishedBuilds(map_branches(branches), 231 num_builds=1)) 232 if builds: 233 b = builds[0] 234 url = (base_builder_url + "/builds/%d" % b.getNumber()) 235 try: 236 label = b.getProperty("got_revision") 237 except KeyError: 238 label = None 239 if not label or len(str(label)) > 20: 240 label = "#%d" % b.getNumber() 241 text = ['<a href="%s">%s</a>' % (url, label)] 242 text.extend(b.getText()) 243 box = Box(text, 244 class_="LastBuild box %s" % build_get_class(b)) 245 data += box.td(align="center") 246 else: 247 data += '<td class="LastBuild box" >no build</td>\n' 248 current_box = ICurrentBox(builder).getBox(status) 249 data += current_box.td(align="center") 250 251 builder_status = builder.getState()[0] 252 if builder_status == "building": 253 building = True 254 online += 1 255 elif builder_status != "offline": 256 online += 1 257 258 data += "</table>\n" 259 260 if control is not None: 261 if building: 262 stopURL = "builders/_all/stop" 263 data += make_stop_form(stopURL, self.isUsingUserPasswd(req), 264 True, "Builds") 265 if online: 266 forceURL = "builders/_all/force" 267 data += make_force_build_form(forceURL, 268 self.isUsingUserPasswd(req), True) 269 270 return data
271 272 273 274 HEADER = ''' 275 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 276 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 277 278 <html 279 xmlns="http://www.w3.org/1999/xhtml" 280 lang="en" 281 xml:lang="en"> 282 ''' 283 284 HEAD_ELEMENTS = [ 285 '<title>%(title)s</title>', 286 '<link href="%(root)sbuildbot.css" rel="stylesheet" type="text/css" />', 287 ] 288 BODY_ATTRS = { 289 'vlink': "#800080", 290 } 291 292 FOOTER = ''' 293 </html> 294 ''' 295 296
297 -class WebStatus(service.MultiService):
298 implements(IStatusReceiver) 299 # TODO: IStatusReceiver is really about things which subscribe to hear 300 # about buildbot events. We need a different interface (perhaps a parent 301 # of IStatusReceiver) for status targets that don't subscribe, like the 302 # WebStatus class. buildbot.master.BuildMaster.loadConfig:737 asserts 303 # that everything in c['status'] provides IStatusReceiver, but really it 304 # should check that they provide IStatusTarget instead. 305 306 """ 307 The webserver provided by this class has the following resources: 308 309 /waterfall : the big time-oriented 'waterfall' display, with links 310 to individual changes, builders, builds, steps, and logs. 311 A number of query-arguments can be added to influence 312 the display. 313 /rss : a rss feed summarizing all failed builds. The same 314 query-arguments used by 'waterfall' can be added to 315 influence the feed output. 316 /atom : an atom feed summarizing all failed builds. The same 317 query-arguments used by 'waterfall' can be added to 318 influence the feed output. 319 /grid : another summary display that shows a grid of builds, with 320 sourcestamps on the x axis, and builders on the y. Query 321 arguments similar to those for the waterfall can be added. 322 /tgrid : similar to the grid display, but the commits are down the 323 left side, and the build hosts are across the top. 324 /builders/BUILDERNAME: a page summarizing the builder. This includes 325 references to the Schedulers that feed it, 326 any builds currently in the queue, which 327 buildslaves are designated or attached, and a 328 summary of the build process it uses. 329 /builders/BUILDERNAME/builds/NUM: a page describing a single Build 330 /builders/BUILDERNAME/builds/NUM/steps/STEPNAME: describes a single step 331 /builders/BUILDERNAME/builds/NUM/steps/STEPNAME/logs/LOGNAME: a StatusLog 332 /builders/BUILDERNAME/builds/NUM/tests : summarize test results 333 /builders/BUILDERNAME/builds/NUM/tests/TEST.NAME: results of one test 334 /builders/_all/{force,stop}: force a build/stop building on all builders. 335 /changes : summarize all ChangeSources 336 /changes/CHANGENUM: a page describing a single Change 337 /schedulers/SCHEDULERNAME: a page describing a Scheduler, including 338 a description of its behavior, a list of the 339 Builders it triggers, and list of the Changes 340 that are queued awaiting the tree-stable 341 timer, and controls to accelerate the timer. 342 /buildslaves : list all BuildSlaves 343 /buildslaves/SLAVENAME : describe a single BuildSlave 344 /one_line_per_build : summarize the last few builds, one line each 345 /one_line_per_build/BUILDERNAME : same, but only for a single builder 346 /one_box_per_builder : show the latest build and current activity 347 /about : describe this buildmaster (Buildbot and support library versions) 348 /xmlrpc : (not yet implemented) an XMLRPC server with build status 349 350 351 All URLs for pages which are not defined here are used to look 352 for files in PUBLIC_HTML, which defaults to BASEDIR/public_html. 353 This means that /robots.txt or /buildbot.css or /favicon.ico can 354 be placed in that directory. 355 356 If an index file (index.html, index.htm, or index, in that order) is 357 present in PUBLIC_HTML, it will be used for the root resource. If not, 358 the default behavior is to put a redirection to the /waterfall page. 359 360 All of the resources provided by this service use relative URLs to reach 361 each other. The only absolute links are the c['projectURL'] links at the 362 top and bottom of the page, and the buildbot home-page link at the 363 bottom. 364 365 This webserver defines class attributes on elements so they can be styled 366 with CSS stylesheets. All pages pull in PUBLIC_HTML/buildbot.css, and you 367 can cause additional stylesheets to be loaded by adding a suitable <link> 368 to the WebStatus instance's .head_elements attribute. 369 370 Buildbot uses some generic classes to identify the type of object, and 371 some more specific classes for the various kinds of those types. It does 372 this by specifying both in the class attributes where applicable, 373 separated by a space. It is important that in your CSS you declare the 374 more generic class styles above the more specific ones. For example, 375 first define a style for .Event, and below that for .SUCCESS 376 377 The following CSS class names are used: 378 - Activity, Event, BuildStep, LastBuild: general classes 379 - waiting, interlocked, building, offline, idle: Activity states 380 - start, running, success, failure, warnings, skipped, exception: 381 LastBuild and BuildStep states 382 - Change: box with change 383 - Builder: box for builder name (at top) 384 - Project 385 - Time 386 387 """ 388 389 # we are not a ComparableMixin, and therefore the webserver will be 390 # rebuilt every time we reconfig. This is because WebStatus.putChild() 391 # makes it too difficult to tell whether two instances are the same or 392 # not (we'd have to do a recursive traversal of all children to discover 393 # all the changes). 394
395 - def __init__(self, http_port=None, distrib_port=None, allowForce=False, 396 public_html="public_html", site=None, numbuilds=20, auth=None):
397 """Run a web server that provides Buildbot status. 398 399 @type http_port: int or L{twisted.application.strports} string 400 @param http_port: a strports specification describing which port the 401 buildbot should use for its web server, with the 402 Waterfall display as the root page. For backwards 403 compatibility this can also be an int. Use 404 'tcp:8000' to listen on that port, or 405 'tcp:12345:interface=127.0.0.1' if you only want 406 local processes to connect to it (perhaps because 407 you are using an HTTP reverse proxy to make the 408 buildbot available to the outside world, and do not 409 want to make the raw port visible). 410 411 @type distrib_port: int or L{twisted.application.strports} string 412 @param distrib_port: Use this if you want to publish the Waterfall 413 page using web.distrib instead. The most common 414 case is to provide a string that is an absolute 415 pathname to the unix socket on which the 416 publisher should listen 417 (C{os.path.expanduser(~/.twistd-web-pb)} will 418 match the default settings of a standard 419 twisted.web 'personal web server'). Another 420 possibility is to pass an integer, which means 421 the publisher should listen on a TCP socket, 422 allowing the web server to be on a different 423 machine entirely. Both forms are provided for 424 backwards compatibility; the preferred form is a 425 strports specification like 426 'unix:/home/buildbot/.twistd-web-pb'. Providing 427 a non-absolute pathname will probably confuse 428 the strports parser. 429 430 @param allowForce: boolean, if True then the webserver will allow 431 visitors to trigger and cancel builds 432 433 @param public_html: the path to the public_html directory for this display, 434 either absolute or relative to the basedir. The default 435 is 'public_html', which selects BASEDIR/public_html. 436 437 @type site: None or L{twisted.web.server.Site} 438 @param site: Use this if you want to define your own object instead of 439 using the default.` 440 441 @type numbuilds: int 442 @param numbuilds: Default number of entries in lists at the /one_line_per_build 443 and /builders/FOO URLs. This default can be overriden both programatically --- 444 by passing the equally named argument to constructors of OneLinePerBuildOneBuilder 445 and OneLinePerBuild --- and via the UI, by tacking ?numbuilds=xy onto the URL. 446 447 @type auth: a L{status.web.auth.IAuth} or C{None} 448 @param auth: an object that performs authentication to restrict access 449 to the C{allowForce} features. Ignored if C{allowForce} 450 is not C{True}. If C{auth} is C{None}, people can force or 451 stop builds without auth. 452 """ 453 454 service.MultiService.__init__(self) 455 if type(http_port) is int: 456 http_port = "tcp:%d" % http_port 457 self.http_port = http_port 458 if distrib_port is not None: 459 if type(distrib_port) is int: 460 distrib_port = "tcp:%d" % distrib_port 461 if distrib_port[0] in "/~.": # pathnames 462 distrib_port = "unix:%s" % distrib_port 463 self.distrib_port = distrib_port 464 self.allowForce = allowForce 465 self.public_html = public_html 466 467 if self.allowForce and auth: 468 assert IAuth.providedBy(auth) 469 self.auth = auth 470 else: 471 if auth: 472 log.msg("Warning: Ignoring authentication. allowForce must be" 473 " set to True use this") 474 self.auth = None 475 476 # If we were given a site object, go ahead and use it. 477 if site: 478 self.site = site 479 else: 480 # this will be replaced once we've been attached to a parent (and 481 # thus have a basedir and can reference BASEDIR) 482 root = static.Data("placeholder", "text/plain") 483 self.site = server.Site(root) 484 self.childrenToBeAdded = {} 485 486 self.setupUsualPages(numbuilds=numbuilds) 487 488 # the following items are accessed by HtmlResource when it renders 489 # each page. 490 self.site.buildbot_service = self 491 self.header = HEADER 492 self.head_elements = HEAD_ELEMENTS[:] 493 self.body_attrs = BODY_ATTRS.copy() 494 self.footer = FOOTER 495 self.template_values = {} 496 497 # keep track of cached connections so we can break them when we shut 498 # down. See ticket #102 for more details. 499 self.channels = weakref.WeakKeyDictionary() 500 501 if self.http_port is not None: 502 s = strports.service(self.http_port, self.site) 503 s.setServiceParent(self) 504 if self.distrib_port is not None: 505 f = pb.PBServerFactory(distrib.ResourcePublisher(self.site)) 506 s = strports.service(self.distrib_port, f) 507 s.setServiceParent(self)
508
509 - def setupUsualPages(self, numbuilds):
510 #self.putChild("", IndexOrWaterfallRedirection()) 511 self.putChild("waterfall", WaterfallStatusResource()) 512 self.putChild("grid", GridStatusResource()) 513 self.putChild("tgrid", TransposedGridStatusResource()) 514 self.putChild("builders", BuildersResource()) # has builds/steps/logs 515 self.putChild("changes", ChangesResource()) 516 self.putChild("buildslaves", BuildSlavesResource()) 517 self.putChild("buildstatus", BuildStatusStatusResource()) 518 #self.putChild("schedulers", SchedulersResource()) 519 self.putChild("one_line_per_build", 520 OneLinePerBuild(numbuilds=numbuilds)) 521 self.putChild("one_box_per_builder", OneBoxPerBuilder()) 522 self.putChild("xmlrpc", XMLRPCServer()) 523 self.putChild("about", AboutBuildbot()) 524 self.putChild("authfail", AuthFailResource())
525
526 - def __repr__(self):
527 if self.http_port is None: 528 return "<WebStatus on path %s at %s>" % (self.distrib_port, 529 hex(id(self))) 530 if self.distrib_port is None: 531 return "<WebStatus on port %s at %s>" % (self.http_port, 532 hex(id(self))) 533 return ("<WebStatus on port %s and path %s at %s>" % 534 (self.http_port, self.distrib_port, hex(id(self))))
535
536 - def setServiceParent(self, parent):
537 service.MultiService.setServiceParent(self, parent) 538 539 # this class keeps a *separate* link to the buildmaster, rather than 540 # just using self.parent, so that when we are "disowned" (and thus 541 # parent=None), any remaining HTTP clients of this WebStatus will still 542 # be able to get reasonable results. 543 self.master = parent 544 545 self.setupSite()
546
547 - def setupSite(self):
548 # this is responsible for creating the root resource. It isn't done 549 # at __init__ time because we need to reference the parent's basedir. 550 htmldir = os.path.abspath(os.path.join(self.master.basedir, self.public_html)) 551 if os.path.isdir(htmldir): 552 log.msg("WebStatus using (%s)" % htmldir) 553 else: 554 log.msg("WebStatus: warning: %s is missing. Do you need to run" 555 " 'buildbot upgrade-master' on this buildmaster?" % htmldir) 556 # all static pages will get a 404 until upgrade-master is used to 557 # populate this directory. Create the directory, though, since 558 # otherwise we get internal server errors instead of 404s. 559 os.mkdir(htmldir) 560 root = static.File(htmldir) 561 562 for name, child_resource in self.childrenToBeAdded.iteritems(): 563 root.putChild(name, child_resource) 564 565 status = self.getStatus() 566 root.putChild("rss", Rss20StatusResource(status)) 567 root.putChild("atom", Atom10StatusResource(status)) 568 569 self.site.resource = root
570
571 - def putChild(self, name, child_resource):
572 """This behaves a lot like root.putChild() . """ 573 self.childrenToBeAdded[name] = child_resource
574
575 - def registerChannel(self, channel):
576 self.channels[channel] = 1 # weakrefs
577
578 - def stopService(self):
579 for channel in self.channels: 580 try: 581 channel.transport.loseConnection() 582 except: 583 log.msg("WebStatus.stopService: error while disconnecting" 584 " leftover clients") 585 log.err() 586 return service.MultiService.stopService(self)
587
588 - def getStatus(self):
589 return self.master.getStatus()
590
591 - def getControl(self):
592 if self.allowForce: 593 return IControl(self.master) 594 return None
595
596 - def getChangeSvc(self):
597 return self.master.change_svc
598
599 - def getPortnum(self):
600 # this is for the benefit of unit tests 601 s = list(self)[0] 602 return s._port.getHost().port
603
604 - def isUsingUserPasswd(self):
605 """Returns boolean to indicate if this WebStatus uses authentication""" 606 if self.auth: 607 return True 608 return False
609
610 - def authUser(self, user, passwd):
611 """Check that user/passwd is a valid user/pass tuple and can should be 612 allowed to perform the action. If this WebStatus is not password 613 protected, this function returns False.""" 614 if not self.isUsingUserPasswd(): 615 return False 616 if self.auth.authenticate(user, passwd): 617 return True 618 log.msg("Authentication failed for '%s': %s" % (user, 619 self.auth.errmsg())) 620 return False
621 622 # resources can get access to the IStatus by calling 623 # request.site.buildbot_service.getStatus() 624 625 # this is the compatibility class for the old waterfall. It is exactly like a 626 # regular WebStatus except that the root resource (e.g. http://buildbot.net/) 627 # always redirects to a WaterfallStatusResource, and the old arguments are 628 # mapped into the new resource-tree approach. In the normal WebStatus, the 629 # root resource either redirects the browser to /waterfall or serves 630 # PUBLIC_HTML/index.html, and favicon/robots.txt are provided by 631 # having the admin write actual files into PUBLIC_HTML/ . 632 633 # note: we don't use a util.Redirect here because HTTP requires that the 634 # Location: header provide an absolute URI, and it's non-trivial to figure 635 # out our absolute URI from here. 636
637 -class Waterfall(WebStatus):
638 639 if hasattr(sys, "frozen"): 640 # all 'data' files are in the directory of our executable 641 here = os.path.dirname(sys.executable) 642 buildbot_icon = os.path.abspath(os.path.join(here, "buildbot.png")) 643 buildbot_css = os.path.abspath(os.path.join(here, "classic.css")) 644 else: 645 # running from source 646 # the icon is sibpath(__file__, "../buildbot.png") . This is for 647 # portability. 648 up = os.path.dirname 649 buildbot_icon = os.path.abspath(os.path.join(up(up(up(__file__))), 650 "buildbot.png")) 651 buildbot_css = os.path.abspath(os.path.join(up(__file__), 652 "classic.css")) 653 654 compare_attrs = ["http_port", "distrib_port", "allowForce", 655 "categories", "css", "favicon", "robots_txt"] 656
657 - def __init__(self, http_port=None, distrib_port=None, allowForce=True, 658 categories=None, css=buildbot_css, favicon=buildbot_icon, 659 robots_txt=None):
660 import warnings 661 m = ("buildbot.status.html.Waterfall is deprecated as of 0.7.6 " 662 "and will be removed from a future release. " 663 "Please use html.WebStatus instead.") 664 warnings.warn(m, DeprecationWarning) 665 666 WebStatus.__init__(self, http_port, distrib_port, allowForce) 667 self.css = css 668 if css: 669 if os.path.exists(os.path.join("public_html", "buildbot.css")): 670 # they've upgraded, so defer to that copy instead 671 pass 672 else: 673 data = open(css, "rb").read() 674 self.putChild("buildbot.css", static.Data(data, "text/css")) 675 self.favicon = favicon 676 self.robots_txt = robots_txt 677 if favicon: 678 data = open(favicon, "rb").read() 679 self.putChild("favicon.ico", static.Data(data, "image/x-icon")) 680 if robots_txt: 681 data = open(robots_txt, "rb").read() 682 self.putChild("robots.txt", static.Data(data, "text/plain")) 683 self.putChild("", WaterfallStatusResource(categories))
684