3 from twisted
.internet
import gtk2reactor
6 from twisted
.internet
import reactor
13 assert(gtk
.Window
) # in gtk1 it's gtk.GtkWindow
15 from twisted
.spread
import pb
17 #from buildbot.clients.base import Builder, Client
18 from buildbot
.clients
.base
import TextClient
19 from buildbot
.util
import now
27 """This is a one-row status bar. It has one square per Builder, and that
28 square is either red, yellow, or green. """
32 self.widget = gtk.VBox(gtk.FALSE, 2)
33 self.nameBox = gtk.HBox(gtk.TRUE)
34 self.statusBox = gtk.HBox(gtk.TRUE)
35 self.widget.add(self.nameBox)
36 self.widget.add(self.statusBox)
37 self.widget.show_all()
42 def addBuilder(self, builder):
43 print "OneRow.addBuilder"
44 # todo: ordering. Should follow the order in which they were added
45 # to the original BotMaster
46 self.builders.append(builder)
47 # add the name to the left column, and a label (with background) to
49 name = gtk.Label(builder.name)
50 status = gtk.Label('??')
51 status.set_size_request(64,64)
56 self.nameBox.add(name)
57 self.statusBox.add(box)
58 builder.haveSomeWidgets([name, status, box])
60 class R2Builder(Builder):
62 self.nameSquare.set_text(self.name)
63 self.statusSquare.set_text("???")
65 def haveSomeWidgets(self, widgets):
66 self.nameSquare, self.statusSquare, self.statusBox = widgets
68 def remote_newLastBuildStatus(self, event):
71 text = "\n".join(event.text)
75 self.statusSquare.set_text(text)
78 self.statusBox.modify_bg(gtk.STATE_NORMAL,
79 gtk.gdk.color_parse(color))
81 def remote_currentlyOffline(self):
82 self.statusSquare.set_text("offline")
83 def remote_currentlyIdle(self):
84 self.statusSquare.set_text("idle")
85 def remote_currentlyWaiting(self, seconds):
86 self.statusSquare.set_text("waiting")
87 def remote_currentlyInterlocked(self):
88 self.statusSquare.set_text("interlocked")
89 def remote_currentlyBuilding(self, eta):
90 self.statusSquare.set_text("building")
93 class CompactRow(Pane):
96 self.widget = gtk.VBox(gtk.FALSE, 3)
97 self.nameBox = gtk.HBox(gtk.TRUE, 2)
98 self.lastBuildBox = gtk.HBox(gtk.TRUE, 2)
99 self.statusBox = gtk.HBox(gtk.TRUE, 2)
100 self.widget.add(self.nameBox)
101 self.widget.add(self.lastBuildBox)
102 self.widget.add(self.statusBox)
103 self.widget.show_all()
109 def addBuilder(self, builder):
110 self.builders.append(builder)
112 name = gtk.Label(builder.name)
114 self.nameBox.add(name)
116 last = gtk.Label('??')
117 last.set_size_request(64,64)
118 lastbox = gtk.EventBox()
121 self.lastBuildBox.add(lastbox)
123 status = gtk.Label('??')
124 status.set_size_request(64,64)
125 statusbox = gtk.EventBox()
126 statusbox.add(status)
128 self.statusBox.add(statusbox)
130 builder.haveSomeWidgets([name, last, lastbox, status, statusbox])
132 def removeBuilder(self, name, builder):
133 self.nameBox.remove(builder.nameSquare)
134 self.lastBuildBox.remove(builder.lastBuildBox)
135 self.statusBox.remove(builder.statusBox)
136 self.builders.remove(builder)
138 class CompactBuilder(Builder):
144 self.nameSquare.set_text(self.name)
145 self.statusSquare.set_text("???")
147 def haveSomeWidgets(self, widgets):
149 self.lastBuildSquare, self.lastBuildBox,
150 self.statusSquare, self.statusBox) = widgets
152 def remote_currentlyOffline(self):
155 self.statusSquare.set_text("offline")
156 self.statusBox.modify_bg(gtk.STATE_NORMAL,
157 gtk.gdk.color_parse("red"))
158 def remote_currentlyIdle(self):
161 self.statusSquare.set_text("idle")
162 def remote_currentlyWaiting(self, seconds):
163 self.nextBuild = now() + seconds
164 self.startTimer(self.updateWaiting)
165 def remote_currentlyInterlocked(self):
167 self.statusSquare.set_text("interlocked")
168 def startTimer(self, func):
169 # the func must clear self.timer and return gtk.FALSE when the event
172 self.timer = gtk.timeout_add(1000, func)
176 gtk.timeout_remove(self.timer)
178 def updateWaiting(self):
179 when = self.nextBuild
181 next = time.strftime("%H:%M:%S", time.localtime(when))
182 secs = "[%d seconds]" % (when - now())
183 self.statusSquare.set_text("waiting\n%s\n%s" % (next, secs))
184 return gtk.TRUE # restart timer
187 self.statusSquare.set_text("waiting\n[RSN]")
191 def remote_currentlyBuilding(self, eta):
193 self.statusSquare.set_text("building")
195 d = eta.callRemote("subscribe", self, 5)
197 def remote_newLastBuildStatus(self, event):
200 text = "\n".join(event.text)
204 if not color: color = "gray"
205 self.lastBuildSquare.set_text(text)
206 self.lastBuildBox.modify_bg(gtk.STATE_NORMAL,
207 gtk.gdk.color_parse(color))
209 def remote_newEvent(self, event):
210 assert(event.__class__ == GtkUpdatingEvent)
213 self.text = event.text
214 if not self.text: self.text = ["idle"]
219 if not color: color = "gray"
220 self.statusBox.modify_bg(gtk.STATE_NORMAL,
221 gtk.gdk.color_parse(color))
223 def updateCurrent(self):
224 text = self.current.text
228 color = self.current.color
230 self.statusBox.modify_bg(gtk.STATE_NORMAL,
231 gtk.gdk.color_parse(color))
232 def updateText(self):
235 etatext = [time.strftime("%H:%M:%S", time.localtime(self.eta))]
239 seconds = self.eta - now()
240 etatext += ["[%d secs]" % seconds]
241 text = "\n".join(self.text + etatext)
242 self.statusSquare.set_text(text)
243 def updateTextTimer(self):
245 return gtk.TRUE # restart timer
247 def remote_progress(self, seconds):
251 self.eta = now() + seconds
252 self.startTimer(self.updateTextTimer)
254 def remote_finished(self, eta):
258 eta.callRemote("unsubscribe", self)
262 def __init__(self
, text
="?"):
264 self
.box
= gtk
.EventBox()
265 self
.label
= gtk
.Label(text
)
266 self
.box
.add(self
.label
)
267 self
.box
.set_size_request(64,64)
273 def setText(self
, text
):
275 self
.label
.set_text(text
)
277 def setColor(self
, color
):
280 self
.box
.modify_bg(gtk
.STATE_NORMAL
, gtk
.gdk
.color_parse(color
))
282 def setETA(self
, eta
):
284 self
.when
= now() + eta
289 def startTimer(self
):
291 self
.timer
= gobject
.timeout_add(1000, self
.update
)
296 gobject
.source_remove(self
.timer
)
298 self
.label
.set_text(self
.text
)
301 if now() < self
.when
:
302 next
= time
.strftime("%H:%M:%S", time
.localtime(self
.when
))
303 secs
= "[%d secs]" % (self
.when
- now())
304 self
.label
.set_text("%s\n%s\n%s" % (self
.text
, next
, secs
))
305 return True # restart timer
308 self
.label
.set_text("%s\n[soon]\n[overdue]" % (self
.text
,))
314 class ThreeRowBuilder
:
315 def __init__(self
, name
, ref
):
320 self
.step
= Box("idle")
321 self
.step
.setColor("white")
326 return self
.last
.getBox(), self
.current
.getBox(), self
.step
.getBox()
328 def getLastBuild(self
):
329 d
= self
.ref
.callRemote("getLastFinishedBuild")
330 d
.addCallback(self
.gotLastBuild
)
331 def gotLastBuild(self
, build
):
333 build
.callRemote("getText").addCallback(self
.gotLastText
)
334 build
.callRemote("getColor").addCallback(self
.gotLastColor
)
336 def gotLastText(self
, text
):
337 self
.last
.setText("\n".join(text
))
338 def gotLastColor(self
, color
):
339 self
.last
.setColor(color
)
342 self
.ref
.callRemote("getState").addCallback(self
.gotState
)
343 def gotState(self
, res
):
344 state
, ETA
, builds
= res
345 # state is one of: offline, idle, waiting, interlocked, building
346 # TODO: ETA is going away, you have to look inside the builds to get
348 currentmap
= {"offline": "red",
351 "interlocked": "yellow",
352 "building": "yellow",}
354 self
.current
.setColor(currentmap
[state
])
356 text
+= "\nETA=%s secs" % ETA
357 self
.current
.setText(state
)
359 def buildStarted(self
, build
):
360 print "[%s] buildStarted" % (self
.name
,)
361 self
.current
.setColor("yellow")
363 def buildFinished(self
, build
, results
):
364 print "[%s] buildFinished: %s" % (self
.name
, results
)
365 self
.gotLastBuild(build
)
366 self
.current
.setColor("white")
367 self
.current
.stopTimer()
369 def buildETAUpdate(self
, eta
):
370 print "[%s] buildETAUpdate: %s" % (self
.name
, eta
)
371 self
.current
.setETA(eta
)
374 def stepStarted(self
, stepname
, step
):
375 print "[%s] stepStarted: %s" % (self
.name
, stepname
)
376 self
.step
.setText(stepname
)
377 self
.step
.setColor("yellow")
378 def stepFinished(self
, stepname
, step
, results
):
379 print "[%s] stepFinished: %s %s" % (self
.name
, stepname
, results
)
380 self
.step
.setText("idle")
381 self
.step
.setColor("white")
382 self
.step
.stopTimer()
383 def stepETAUpdate(self
, stepname
, eta
):
384 print "[%s] stepETAUpdate: %s %s" % (self
.name
, stepname
, eta
)
385 self
.step
.setETA(eta
)
388 class ThreeRowClient(pb
.Referenceable
):
389 def __init__(self
, window
):
391 self
.buildernames
= []
394 def connected(self
, ref
):
397 self
.pane
= gtk
.VBox(False, 2)
398 self
.table
= gtk
.Table(1+3, 1)
399 self
.pane
.add(self
.table
)
400 self
.window
.vb
.add(self
.pane
)
402 ref
.callRemote("subscribe", "logs", 5, self
)
404 def removeTable(self
):
405 for child
in self
.table
.get_children():
406 self
.table
.remove(child
)
407 self
.pane
.remove(self
.table
)
410 columns
= len(self
.builders
)
411 self
.table
= gtk
.Table(2, columns
)
412 self
.pane
.add(self
.table
)
413 for i
in range(len(self
.buildernames
)):
414 name
= self
.buildernames
[i
]
415 b
= self
.builders
[name
]
416 last
,current
,step
= b
.getBoxes()
417 self
.table
.attach(gtk
.Label(name
), i
, i
+1, 0, 1)
418 self
.table
.attach(last
, i
, i
+1, 1, 2,
419 xpadding
=1, ypadding
=1)
420 self
.table
.attach(current
, i
, i
+1, 2, 3,
421 xpadding
=1, ypadding
=1)
422 self
.table
.attach(step
, i
, i
+1, 3, 4,
423 xpadding
=1, ypadding
=1)
424 self
.table
.show_all()
426 def rebuildTable(self
):
430 def remote_builderAdded(self
, buildername
, builder
):
431 print "builderAdded", buildername
432 assert buildername
not in self
.buildernames
433 self
.buildernames
.append(buildername
)
435 b
= ThreeRowBuilder(buildername
, builder
)
436 self
.builders
[buildername
] = b
441 def remote_builderRemoved(self
, buildername
):
442 del self
.builders
[buildername
]
443 self
.buildernames
.remove(buildername
)
446 def remote_builderChangedState(self
, name
, state
, eta
):
447 self
.builders
[name
].gotState((state
, eta
, None))
448 def remote_buildStarted(self
, name
, build
):
449 self
.builders
[name
].buildStarted(build
)
450 def remote_buildFinished(self
, name
, build
, results
):
451 self
.builders
[name
].buildFinished(build
, results
)
453 def remote_buildETAUpdate(self
, name
, build
, eta
):
454 self
.builders
[name
].buildETAUpdate(eta
)
455 def remote_stepStarted(self
, name
, build
, stepname
, step
):
456 self
.builders
[name
].stepStarted(stepname
, step
)
457 def remote_stepFinished(self
, name
, build
, stepname
, step
, results
):
458 self
.builders
[name
].stepFinished(stepname
, step
, results
)
460 def remote_stepETAUpdate(self
, name
, build
, stepname
, step
,
462 # expectations is a list of (metricname, current_value,
463 # expected_value) tuples, so that we could show individual progress
464 # meters for each metric
465 self
.builders
[name
].stepETAUpdate(stepname
, eta
)
467 def remote_logStarted(self
, buildername
, build
, stepname
, step
,
471 def remote_logFinished(self
, buildername
, build
, stepname
, step
,
476 class GtkClient(TextClient
):
477 ClientClass
= ThreeRowClient
479 def __init__(self
, master
):
484 #w.set_size_request(64,64)
485 w
.connect('destroy', lambda win
: gtk
.main_quit())
486 self
.vb
= gtk
.VBox(False, 2)
487 self
.status
= gtk
.Label("unconnected")
488 self
.vb
.add(self
.status
)
489 self
.listener
= self
.ClientClass(self
)
493 def connected(self
, ref
):
494 self
.status
.set_text("connected")
495 TextClient
.connected(self
, ref
)
498 def addBuilder(self, name, builder):
499 Client.addBuilder(self, name, builder)
500 self.pane.addBuilder(builder)
501 def removeBuilder(self, name):
502 self.pane.removeBuilder(name, self.builders[name])
503 Client.removeBuilder(self, name)
505 def startConnecting(self, master):
507 Client.startConnecting(self, master)
508 self.status.set_text("connecting to %s.." % master)
509 def connected(self, remote):
510 Client.connected(self, remote)
511 self.status.set_text(self.master)
512 remote.notifyOnDisconnect(self.disconnected)
513 def disconnected(self, remote):
514 self.status.set_text("disconnected, will retry")
518 master
= "localhost:8007"
519 if len(sys
.argv
) > 1:
521 c
= GtkClient(master
)
524 if __name__
== '__main__':