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)
11
12 from twisted.spread import pb
13
14
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
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
271
272 - def setText(self, text):
273 self.text = text
274 self.label.set_text(text)
275
277 if not color:
278 return
279 self.box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
280
287
292
294 if self.timer:
295 gobject.source_remove(self.timer)
296 self.timer = None
297 self.label.set_text(self.text)
298
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
305 else:
306
307 self.label.set_text("%s\n[soon]\n[overdue]" % (self.text,))
308 self.timer = None
309 return False
310
311
312
323
326
328 d = self.ref.callRemote("getLastFinishedBuild")
329 d.addCallback(self.gotLastBuild)
334
335 - def gotLastText(self, text):
336 print "Got text", text
337 self.last.setText("\n".join(text))
338
346
348 self.ref.callRemote("getState").addCallback(self.gotState)
350 state, ETA, builds = res
351
352
353
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
366 print "[%s] buildStarted" % (self.name,)
367 self.current.setColor("yellow")
368
374
376 print "[%s] buildETAUpdate: %s" % (self.name, eta)
377 self.current.setETA(eta)
378
379
392
393
396 self.window = window
397 self.buildernames = []
398 self.builders = {}
399
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
411 for child in self.table.get_children():
412 self.table.remove(child)
413 self.pane.remove(self.table)
414
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
435
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
448 del self.builders[buildername]
449 self.buildernames.remove(buildername)
450 self.rebuildTable()
451
458
465
472
476
480
481
483 ClientClass = ThreeRowClient
484
486 self.master = master
487
488 w = gtk.Window()
489 self.w = w
490
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
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
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