| OLD | NEW |
| (Empty) |
| 1 | |
| 2 from twisted.internet import gtk2reactor | |
| 3 gtk2reactor.install() | |
| 4 | |
| 5 import sys, time | |
| 6 | |
| 7 import pygtk | |
| 8 pygtk.require("2.0") | |
| 9 import gobject, gtk | |
| 10 assert(gtk.Window) # in gtk1 it's gtk.GtkWindow | |
| 11 | |
| 12 from twisted.spread import pb | |
| 13 | |
| 14 #from buildbot.clients.base import Builder, Client | |
| 15 from buildbot.clients.base import TextClient | |
| 16 from buildbot.util import now | |
| 17 | |
| 18 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, EXCEPTION | |
| 19 | |
| 20 ''' | |
| 21 class Pane: | |
| 22 def __init__(self): | |
| 23 pass | |
| 24 | |
| 25 class OneRow(Pane): | |
| 26 """This is a one-row status bar. It has one square per Builder, and that | |
| 27 square is either red, yellow, or green. """ | |
| 28 | |
| 29 def __init__(self): | |
| 30 Pane.__init__(self) | |
| 31 self.widget = gtk.VBox(gtk.FALSE, 2) | |
| 32 self.nameBox = gtk.HBox(gtk.TRUE) | |
| 33 self.statusBox = gtk.HBox(gtk.TRUE) | |
| 34 self.widget.add(self.nameBox) | |
| 35 self.widget.add(self.statusBox) | |
| 36 self.widget.show_all() | |
| 37 self.builders = [] | |
| 38 | |
| 39 def getWidget(self): | |
| 40 return self.widget | |
| 41 def addBuilder(self, builder): | |
| 42 print "OneRow.addBuilder" | |
| 43 # todo: ordering. Should follow the order in which they were added | |
| 44 # to the original BotMaster | |
| 45 self.builders.append(builder) | |
| 46 # add the name to the left column, and a label (with background) to | |
| 47 # the right | |
| 48 name = gtk.Label(builder.name) | |
| 49 status = gtk.Label('??') | |
| 50 status.set_size_request(64,64) | |
| 51 box = gtk.EventBox() | |
| 52 box.add(status) | |
| 53 name.show() | |
| 54 box.show_all() | |
| 55 self.nameBox.add(name) | |
| 56 self.statusBox.add(box) | |
| 57 builder.haveSomeWidgets([name, status, box]) | |
| 58 | |
| 59 class R2Builder(Builder): | |
| 60 def start(self): | |
| 61 self.nameSquare.set_text(self.name) | |
| 62 self.statusSquare.set_text("???") | |
| 63 self.subscribe() | |
| 64 def haveSomeWidgets(self, widgets): | |
| 65 self.nameSquare, self.statusSquare, self.statusBox = widgets | |
| 66 | |
| 67 def remote_newLastBuildStatus(self, event): | |
| 68 color = None | |
| 69 if event: | |
| 70 text = "\n".join(event.text) | |
| 71 color = event.color | |
| 72 else: | |
| 73 text = "none" | |
| 74 self.statusSquare.set_text(text) | |
| 75 if color: | |
| 76 print "color", color | |
| 77 self.statusBox.modify_bg(gtk.STATE_NORMAL, | |
| 78 gtk.gdk.color_parse(color)) | |
| 79 | |
| 80 def remote_currentlyOffline(self): | |
| 81 self.statusSquare.set_text("offline") | |
| 82 def remote_currentlyIdle(self): | |
| 83 self.statusSquare.set_text("idle") | |
| 84 def remote_currentlyWaiting(self, seconds): | |
| 85 self.statusSquare.set_text("waiting") | |
| 86 def remote_currentlyInterlocked(self): | |
| 87 self.statusSquare.set_text("interlocked") | |
| 88 def remote_currentlyBuilding(self, eta): | |
| 89 self.statusSquare.set_text("building") | |
| 90 | |
| 91 | |
| 92 class CompactRow(Pane): | |
| 93 def __init__(self): | |
| 94 Pane.__init__(self) | |
| 95 self.widget = gtk.VBox(gtk.FALSE, 3) | |
| 96 self.nameBox = gtk.HBox(gtk.TRUE, 2) | |
| 97 self.lastBuildBox = gtk.HBox(gtk.TRUE, 2) | |
| 98 self.statusBox = gtk.HBox(gtk.TRUE, 2) | |
| 99 self.widget.add(self.nameBox) | |
| 100 self.widget.add(self.lastBuildBox) | |
| 101 self.widget.add(self.statusBox) | |
| 102 self.widget.show_all() | |
| 103 self.builders = [] | |
| 104 | |
| 105 def getWidget(self): | |
| 106 return self.widget | |
| 107 | |
| 108 def addBuilder(self, builder): | |
| 109 self.builders.append(builder) | |
| 110 | |
| 111 name = gtk.Label(builder.name) | |
| 112 name.show() | |
| 113 self.nameBox.add(name) | |
| 114 | |
| 115 last = gtk.Label('??') | |
| 116 last.set_size_request(64,64) | |
| 117 lastbox = gtk.EventBox() | |
| 118 lastbox.add(last) | |
| 119 lastbox.show_all() | |
| 120 self.lastBuildBox.add(lastbox) | |
| 121 | |
| 122 status = gtk.Label('??') | |
| 123 status.set_size_request(64,64) | |
| 124 statusbox = gtk.EventBox() | |
| 125 statusbox.add(status) | |
| 126 statusbox.show_all() | |
| 127 self.statusBox.add(statusbox) | |
| 128 | |
| 129 builder.haveSomeWidgets([name, last, lastbox, status, statusbox]) | |
| 130 | |
| 131 def removeBuilder(self, name, builder): | |
| 132 self.nameBox.remove(builder.nameSquare) | |
| 133 self.lastBuildBox.remove(builder.lastBuildBox) | |
| 134 self.statusBox.remove(builder.statusBox) | |
| 135 self.builders.remove(builder) | |
| 136 | |
| 137 class CompactBuilder(Builder): | |
| 138 def setup(self): | |
| 139 self.timer = None | |
| 140 self.text = [] | |
| 141 self.eta = None | |
| 142 def start(self): | |
| 143 self.nameSquare.set_text(self.name) | |
| 144 self.statusSquare.set_text("???") | |
| 145 self.subscribe() | |
| 146 def haveSomeWidgets(self, widgets): | |
| 147 (self.nameSquare, | |
| 148 self.lastBuildSquare, self.lastBuildBox, | |
| 149 self.statusSquare, self.statusBox) = widgets | |
| 150 | |
| 151 def remote_currentlyOffline(self): | |
| 152 self.eta = None | |
| 153 self.stopTimer() | |
| 154 self.statusSquare.set_text("offline") | |
| 155 self.statusBox.modify_bg(gtk.STATE_NORMAL, | |
| 156 gtk.gdk.color_parse("red")) | |
| 157 def remote_currentlyIdle(self): | |
| 158 self.eta = None | |
| 159 self.stopTimer() | |
| 160 self.statusSquare.set_text("idle") | |
| 161 def remote_currentlyWaiting(self, seconds): | |
| 162 self.nextBuild = now() + seconds | |
| 163 self.startTimer(self.updateWaiting) | |
| 164 def remote_currentlyInterlocked(self): | |
| 165 self.stopTimer() | |
| 166 self.statusSquare.set_text("interlocked") | |
| 167 def startTimer(self, func): | |
| 168 # the func must clear self.timer and return gtk.FALSE when the event | |
| 169 # has arrived | |
| 170 self.stopTimer() | |
| 171 self.timer = gtk.timeout_add(1000, func) | |
| 172 func() | |
| 173 def stopTimer(self): | |
| 174 if self.timer: | |
| 175 gtk.timeout_remove(self.timer) | |
| 176 self.timer = None | |
| 177 def updateWaiting(self): | |
| 178 when = self.nextBuild | |
| 179 if now() < when: | |
| 180 next = time.strftime("%H:%M:%S", time.localtime(when)) | |
| 181 secs = "[%d seconds]" % (when - now()) | |
| 182 self.statusSquare.set_text("waiting\n%s\n%s" % (next, secs)) | |
| 183 return gtk.TRUE # restart timer | |
| 184 else: | |
| 185 # done | |
| 186 self.statusSquare.set_text("waiting\n[RSN]") | |
| 187 self.timer = None | |
| 188 return gtk.FALSE | |
| 189 | |
| 190 def remote_currentlyBuilding(self, eta): | |
| 191 self.stopTimer() | |
| 192 self.statusSquare.set_text("building") | |
| 193 if eta: | |
| 194 d = eta.callRemote("subscribe", self, 5) | |
| 195 | |
| 196 def remote_newLastBuildStatus(self, event): | |
| 197 color = None | |
| 198 if event: | |
| 199 text = "\n".join(event.text) | |
| 200 color = event.color | |
| 201 else: | |
| 202 text = "none" | |
| 203 if not color: color = "gray" | |
| 204 self.lastBuildSquare.set_text(text) | |
| 205 self.lastBuildBox.modify_bg(gtk.STATE_NORMAL, | |
| 206 gtk.gdk.color_parse(color)) | |
| 207 | |
| 208 def remote_newEvent(self, event): | |
| 209 assert(event.__class__ == GtkUpdatingEvent) | |
| 210 self.current = event | |
| 211 event.builder = self | |
| 212 self.text = event.text | |
| 213 if not self.text: self.text = ["idle"] | |
| 214 self.eta = None | |
| 215 self.stopTimer() | |
| 216 self.updateText() | |
| 217 color = event.color | |
| 218 if not color: color = "gray" | |
| 219 self.statusBox.modify_bg(gtk.STATE_NORMAL, | |
| 220 gtk.gdk.color_parse(color)) | |
| 221 | |
| 222 def updateCurrent(self): | |
| 223 text = self.current.text | |
| 224 if text: | |
| 225 self.text = text | |
| 226 self.updateText() | |
| 227 color = self.current.color | |
| 228 if color: | |
| 229 self.statusBox.modify_bg(gtk.STATE_NORMAL, | |
| 230 gtk.gdk.color_parse(color)) | |
| 231 def updateText(self): | |
| 232 etatext = [] | |
| 233 if self.eta: | |
| 234 etatext = [time.strftime("%H:%M:%S", time.localtime(self.eta))] | |
| 235 if now() > self.eta: | |
| 236 etatext += ["RSN"] | |
| 237 else: | |
| 238 seconds = self.eta - now() | |
| 239 etatext += ["[%d secs]" % seconds] | |
| 240 text = "\n".join(self.text + etatext) | |
| 241 self.statusSquare.set_text(text) | |
| 242 def updateTextTimer(self): | |
| 243 self.updateText() | |
| 244 return gtk.TRUE # restart timer | |
| 245 | |
| 246 def remote_progress(self, seconds): | |
| 247 if seconds == None: | |
| 248 self.eta = None | |
| 249 else: | |
| 250 self.eta = now() + seconds | |
| 251 self.startTimer(self.updateTextTimer) | |
| 252 self.updateText() | |
| 253 def remote_finished(self, eta): | |
| 254 self.eta = None | |
| 255 self.stopTimer() | |
| 256 self.updateText() | |
| 257 eta.callRemote("unsubscribe", self) | |
| 258 ''' | |
| 259 | |
| 260 class Box: | |
| 261 def __init__(self, text="?"): | |
| 262 self.text = text | |
| 263 self.box = gtk.EventBox() | |
| 264 self.label = gtk.Label(text) | |
| 265 self.box.add(self.label) | |
| 266 self.box.set_size_request(64,64) | |
| 267 self.timer = None | |
| 268 | |
| 269 def getBox(self): | |
| 270 return self.box | |
| 271 | |
| 272 def setText(self, text): | |
| 273 self.text = text | |
| 274 self.label.set_text(text) | |
| 275 | |
| 276 def setColor(self, color): | |
| 277 if not color: | |
| 278 return | |
| 279 self.box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) | |
| 280 | |
| 281 def setETA(self, eta): | |
| 282 if eta: | |
| 283 self.when = now() + eta | |
| 284 self.startTimer() | |
| 285 else: | |
| 286 self.stopTimer() | |
| 287 | |
| 288 def startTimer(self): | |
| 289 self.stopTimer() | |
| 290 self.timer = gobject.timeout_add(1000, self.update) | |
| 291 self.update() | |
| 292 | |
| 293 def stopTimer(self): | |
| 294 if self.timer: | |
| 295 gobject.source_remove(self.timer) | |
| 296 self.timer = None | |
| 297 self.label.set_text(self.text) | |
| 298 | |
| 299 def update(self): | |
| 300 if now() < self.when: | |
| 301 next = time.strftime("%H:%M:%S", time.localtime(self.when)) | |
| 302 secs = "[%d secs]" % (self.when - now()) | |
| 303 self.label.set_text("%s\n%s\n%s" % (self.text, next, secs)) | |
| 304 return True # restart timer | |
| 305 else: | |
| 306 # done | |
| 307 self.label.set_text("%s\n[soon]\n[overdue]" % (self.text,)) | |
| 308 self.timer = None | |
| 309 return False | |
| 310 | |
| 311 | |
| 312 | |
| 313 class ThreeRowBuilder: | |
| 314 def __init__(self, name, ref): | |
| 315 self.name = name | |
| 316 | |
| 317 self.last = Box() | |
| 318 self.current = Box() | |
| 319 self.step = Box("idle") | |
| 320 self.step.setColor("white") | |
| 321 | |
| 322 self.ref = ref | |
| 323 | |
| 324 def getBoxes(self): | |
| 325 return self.last.getBox(), self.current.getBox(), self.step.getBox() | |
| 326 | |
| 327 def getLastBuild(self): | |
| 328 d = self.ref.callRemote("getLastFinishedBuild") | |
| 329 d.addCallback(self.gotLastBuild) | |
| 330 def gotLastBuild(self, build): | |
| 331 if build: | |
| 332 build.callRemote("getText").addCallback(self.gotLastText) | |
| 333 build.callRemote("getResults").addCallback(self.gotLastResult) | |
| 334 | |
| 335 def gotLastText(self, text): | |
| 336 print "Got text", text | |
| 337 self.last.setText("\n".join(text)) | |
| 338 | |
| 339 def gotLastResult(self, result): | |
| 340 colormap = {SUCCESS: 'green', | |
| 341 FAILURE: 'red', | |
| 342 WARNINGS: 'orange', | |
| 343 EXCEPTION: 'purple', | |
| 344 } | |
| 345 self.last.setColor(colormap[result]) | |
| 346 | |
| 347 def getState(self): | |
| 348 self.ref.callRemote("getState").addCallback(self.gotState) | |
| 349 def gotState(self, res): | |
| 350 state, ETA, builds = res | |
| 351 # state is one of: offline, idle, waiting, interlocked, building | |
| 352 # TODO: ETA is going away, you have to look inside the builds to get | |
| 353 # that value | |
| 354 currentmap = {"offline": "red", | |
| 355 "idle": "white", | |
| 356 "waiting": "yellow", | |
| 357 "interlocked": "yellow", | |
| 358 "building": "yellow",} | |
| 359 text = state | |
| 360 self.current.setColor(currentmap[state]) | |
| 361 if ETA is not None: | |
| 362 text += "\nETA=%s secs" % ETA | |
| 363 self.current.setText(state) | |
| 364 | |
| 365 def buildStarted(self, build): | |
| 366 print "[%s] buildStarted" % (self.name,) | |
| 367 self.current.setColor("yellow") | |
| 368 | |
| 369 def buildFinished(self, build, results): | |
| 370 print "[%s] buildFinished: %s" % (self.name, results) | |
| 371 self.gotLastBuild(build) | |
| 372 self.current.setColor("white") | |
| 373 self.current.stopTimer() | |
| 374 | |
| 375 def buildETAUpdate(self, eta): | |
| 376 print "[%s] buildETAUpdate: %s" % (self.name, eta) | |
| 377 self.current.setETA(eta) | |
| 378 | |
| 379 | |
| 380 def stepStarted(self, stepname, step): | |
| 381 print "[%s] stepStarted: %s" % (self.name, stepname) | |
| 382 self.step.setText(stepname) | |
| 383 self.step.setColor("yellow") | |
| 384 def stepFinished(self, stepname, step, results): | |
| 385 print "[%s] stepFinished: %s %s" % (self.name, stepname, results) | |
| 386 self.step.setText("idle") | |
| 387 self.step.setColor("white") | |
| 388 self.step.stopTimer() | |
| 389 def stepETAUpdate(self, stepname, eta): | |
| 390 print "[%s] stepETAUpdate: %s %s" % (self.name, stepname, eta) | |
| 391 self.step.setETA(eta) | |
| 392 | |
| 393 | |
| 394 class ThreeRowClient(pb.Referenceable): | |
| 395 def __init__(self, window): | |
| 396 self.window = window | |
| 397 self.buildernames = [] | |
| 398 self.builders = {} | |
| 399 | |
| 400 def connected(self, ref): | |
| 401 print "connected" | |
| 402 self.ref = ref | |
| 403 self.pane = gtk.VBox(False, 2) | |
| 404 self.table = gtk.Table(1+3, 1) | |
| 405 self.pane.add(self.table) | |
| 406 self.window.vb.add(self.pane) | |
| 407 self.pane.show_all() | |
| 408 ref.callRemote("subscribe", "logs", 5, self) | |
| 409 | |
| 410 def removeTable(self): | |
| 411 for child in self.table.get_children(): | |
| 412 self.table.remove(child) | |
| 413 self.pane.remove(self.table) | |
| 414 | |
| 415 def makeTable(self): | |
| 416 columns = len(self.builders) | |
| 417 self.table = gtk.Table(2, columns) | |
| 418 self.pane.add(self.table) | |
| 419 for i in range(len(self.buildernames)): | |
| 420 name = self.buildernames[i] | |
| 421 b = self.builders[name] | |
| 422 last,current,step = b.getBoxes() | |
| 423 self.table.attach(gtk.Label(name), i, i+1, 0, 1) | |
| 424 self.table.attach(last, i, i+1, 1, 2, | |
| 425 xpadding=1, ypadding=1) | |
| 426 self.table.attach(current, i, i+1, 2, 3, | |
| 427 xpadding=1, ypadding=1) | |
| 428 self.table.attach(step, i, i+1, 3, 4, | |
| 429 xpadding=1, ypadding=1) | |
| 430 self.table.show_all() | |
| 431 | |
| 432 def rebuildTable(self): | |
| 433 self.removeTable() | |
| 434 self.makeTable() | |
| 435 | |
| 436 def remote_builderAdded(self, buildername, builder): | |
| 437 print "builderAdded", buildername | |
| 438 assert buildername not in self.buildernames | |
| 439 self.buildernames.append(buildername) | |
| 440 | |
| 441 b = ThreeRowBuilder(buildername, builder) | |
| 442 self.builders[buildername] = b | |
| 443 self.rebuildTable() | |
| 444 b.getLastBuild() | |
| 445 b.getState() | |
| 446 | |
| 447 def remote_builderRemoved(self, buildername): | |
| 448 del self.builders[buildername] | |
| 449 self.buildernames.remove(buildername) | |
| 450 self.rebuildTable() | |
| 451 | |
| 452 def remote_builderChangedState(self, name, state, eta): | |
| 453 self.builders[name].gotState((state, eta, None)) | |
| 454 def remote_buildStarted(self, name, build): | |
| 455 self.builders[name].buildStarted(build) | |
| 456 def remote_buildFinished(self, name, build, results): | |
| 457 self.builders[name].buildFinished(build, results) | |
| 458 | |
| 459 def remote_buildETAUpdate(self, name, build, eta): | |
| 460 self.builders[name].buildETAUpdate(eta) | |
| 461 def remote_stepStarted(self, name, build, stepname, step): | |
| 462 self.builders[name].stepStarted(stepname, step) | |
| 463 def remote_stepFinished(self, name, build, stepname, step, results): | |
| 464 self.builders[name].stepFinished(stepname, step, results) | |
| 465 | |
| 466 def remote_stepETAUpdate(self, name, build, stepname, step, | |
| 467 eta, expectations): | |
| 468 # expectations is a list of (metricname, current_value, | |
| 469 # expected_value) tuples, so that we could show individual progress | |
| 470 # meters for each metric | |
| 471 self.builders[name].stepETAUpdate(stepname, eta) | |
| 472 | |
| 473 def remote_logStarted(self, buildername, build, stepname, step, | |
| 474 logname, log): | |
| 475 pass | |
| 476 | |
| 477 def remote_logFinished(self, buildername, build, stepname, step, | |
| 478 logname, log): | |
| 479 pass | |
| 480 | |
| 481 | |
| 482 class GtkClient(TextClient): | |
| 483 ClientClass = ThreeRowClient | |
| 484 | |
| 485 def __init__(self, master): | |
| 486 self.master = master | |
| 487 | |
| 488 w = gtk.Window() | |
| 489 self.w = w | |
| 490 #w.set_size_request(64,64) | |
| 491 w.connect('destroy', lambda win: gtk.main_quit()) | |
| 492 self.vb = gtk.VBox(False, 2) | |
| 493 self.status = gtk.Label("unconnected") | |
| 494 self.vb.add(self.status) | |
| 495 self.listener = self.ClientClass(self) | |
| 496 w.add(self.vb) | |
| 497 w.show_all() | |
| 498 | |
| 499 def connected(self, ref): | |
| 500 self.status.set_text("connected") | |
| 501 TextClient.connected(self, ref) | |
| 502 | |
| 503 """ | |
| 504 def addBuilder(self, name, builder): | |
| 505 Client.addBuilder(self, name, builder) | |
| 506 self.pane.addBuilder(builder) | |
| 507 def removeBuilder(self, name): | |
| 508 self.pane.removeBuilder(name, self.builders[name]) | |
| 509 Client.removeBuilder(self, name) | |
| 510 | |
| 511 def startConnecting(self, master): | |
| 512 self.master = master | |
| 513 Client.startConnecting(self, master) | |
| 514 self.status.set_text("connecting to %s.." % master) | |
| 515 def connected(self, remote): | |
| 516 Client.connected(self, remote) | |
| 517 self.status.set_text(self.master) | |
| 518 remote.notifyOnDisconnect(self.disconnected) | |
| 519 def disconnected(self, remote): | |
| 520 self.status.set_text("disconnected, will retry") | |
| 521 """ | |
| 522 | |
| 523 def main(): | |
| 524 master = "localhost:8007" | |
| 525 if len(sys.argv) > 1: | |
| 526 master = sys.argv[1] | |
| 527 c = GtkClient(master) | |
| 528 c.run() | |
| 529 | |
| 530 if __name__ == '__main__': | |
| 531 main() | |
| 532 | |
| OLD | NEW |