web: Builder/Build: use a relative link to the welcome page
[buildbot.git] / buildbot / status / web / builder.py
blob099e7c13e323aff5fbe59aa7fbcb4780fb32742b
2 from twisted.web.error import NoResource
3 from twisted.web import html, static
4 from twisted.web.util import Redirect
6 import re, urllib, time
7 from twisted.python import log
8 from buildbot import interfaces
9 from buildbot.status.web.base import HtmlResource, make_row, css_classes
10 from buildbot.process.base import BuildRequest
11 from buildbot.sourcestamp import SourceStamp
13 from buildbot.status.web.build import BuildsResource
15 # builders/$builder
16 class StatusResourceBuilder(HtmlResource):
17 addSlash = True
19 def __init__(self, builder_status, builder_control):
20 HtmlResource.__init__(self)
21 self.builder_status = builder_status
22 self.builder_control = builder_control
24 def getTitle(self, request):
25 return "Buildbot: %s" % html.escape(self.builder_status.getName())
27 def build_line(self, build, req):
28 buildnum = build.getNumber()
29 buildurl = "builds/%d" % buildnum
30 data = '<a href="%s">#%d</a> ' % (buildurl, buildnum)
31 when = build.getETA()
32 if when is not None:
33 when_time = time.strftime("%H:%M:%S",
34 time.localtime(time.time() + when))
35 data += "ETA %ds (%s) " % (when, when_time)
36 data += "[%s]" % build.getCurrentStep().getName()
37 return data
39 def build_finished_line(self, build, req):
40 buildnum = build.getNumber()
41 buildurl = "builds/%d" % buildnum
42 results = build.getResults()
43 text = " ".join(build.getText())
44 data = '<a href="%s">#%d</a> ' % (buildurl, buildnum)
45 data += '<span class="%s">%s</span>' % (css_classes[results], text)
46 return data
48 def body(self, req):
49 b = self.builder_status
50 control = self.builder_control
51 status = self.getStatus(req)
53 slaves = b.getSlaves()
54 connected_slaves = [s for s in slaves if s.isConnected()]
56 projectName = status.getProjectName()
58 data = '<a href="../..">%s</a>\n' % projectName
60 data += "<h1>Builder: %s</h1>\n" % html.escape(b.getName())
62 # the first section shows builds which are currently running, if any.
64 current = b.getCurrentBuilds()
65 if current:
66 data += "<h2>Currently Building:</h2>\n"
67 data += "<ul>\n"
68 for build in current:
69 data += " <li>" + self.build_line(build, req) + "</li>\n"
70 data += "</ul>\n"
71 else:
72 data += "<h2>no current builds</h2>\n"
74 # Then a section with the last 5 builds, with the most recent build
75 # distinguished from the rest.
77 data += "<h2>Recent Builds:</h2>\n"
78 data += "<ul>\n"
79 for i,build in enumerate(b.generateFinishedBuilds(num_builds=5)):
80 data += " <li>" + self.build_finished_line(build, req) + "</li>\n"
81 if i == 0:
82 data += "<br />\n" # separator
83 # TODO: or empty list?
84 data += "</ul>\n"
87 data += "<h2>Buildslaves:</h2>\n"
88 data += "<ol>\n"
89 for slave in slaves:
90 data += "<li><b>%s</b>: " % html.escape(slave.getName())
91 if slave.isConnected():
92 data += "CONNECTED\n"
93 if slave.getAdmin():
94 data += make_row("Admin:", html.escape(slave.getAdmin()))
95 if slave.getHost():
96 data += "<span class='label'>Host info:</span>\n"
97 data += html.PRE(slave.getHost())
98 else:
99 data += ("NOT CONNECTED\n")
100 data += "</li>\n"
101 data += "</ol>\n"
103 if control is not None and connected_slaves:
104 forceURL = urllib.quote(req.childLink("force"))
105 data += (
107 <form action='%(forceURL)s' class='command forcebuild'>
108 <p>To force a build, fill out the following fields and
109 push the 'Force Build' button</p>"""
110 + make_row("Your name:",
111 "<input type='text' name='username' />")
112 + make_row("Reason for build:",
113 "<input type='text' name='comments' />")
114 + make_row("Branch to build:",
115 "<input type='text' name='branch' />")
116 + make_row("Revision to build:",
117 "<input type='text' name='revision' />")
118 + """
119 <input type='submit' value='Force Build' />
120 </form>
121 """) % {"forceURL": forceURL}
122 elif control is not None:
123 data += """
124 <p>All buildslaves appear to be offline, so it's not possible
125 to force this build to execute at this time.</p>
128 if control is not None:
129 pingURL = urllib.quote(req.childLink("ping"))
130 data += """
131 <form action="%s" class='command pingbuilder'>
132 <p>To ping the buildslave(s), push the 'Ping' button</p>
134 <input type="submit" value="Ping Builder" />
135 </form>
136 """ % pingURL
138 return data
140 def force(self, req):
141 name = req.args.get("username", ["<unknown>"])[0]
142 reason = req.args.get("comments", ["<no reason specified>"])[0]
143 branch = req.args.get("branch", [""])[0]
144 revision = req.args.get("revision", [""])[0]
146 r = "The web-page 'force build' button was pressed by '%s': %s\n" \
147 % (name, reason)
148 log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'"
149 % (self.builder_status.getName(), branch, revision))
151 if not self.builder_control:
152 # TODO: tell the web user that their request was denied
153 log.msg("but builder control is disabled")
154 return Redirect("..")
156 # keep weird stuff out of the branch and revision strings. TODO:
157 # centralize this somewhere.
158 if not re.match(r'^[\w\.\-\/]*$', branch):
159 log.msg("bad branch '%s'" % branch)
160 return Redirect("..")
161 if not re.match(r'^[\w\.\-\/]*$', revision):
162 log.msg("bad revision '%s'" % revision)
163 return Redirect("..")
164 if branch == "":
165 branch = None
166 if revision == "":
167 revision = None
169 # TODO: if we can authenticate that a particular User pushed the
170 # button, use their name instead of None, so they'll be informed of
171 # the results.
172 s = SourceStamp(branch=branch, revision=revision)
173 req = BuildRequest(r, s, self.builder_status.getName())
174 try:
175 self.builder_control.requestBuildSoon(req)
176 except interfaces.NoSlaveError:
177 # TODO: tell the web user that their request could not be
178 # honored
179 pass
180 return Redirect("../..")
182 def ping(self, req):
183 log.msg("web ping of builder '%s'" % self.builder_status.getName())
184 self.builder_control.ping() # TODO: there ought to be an ISlaveControl
185 return Redirect("../..")
187 def getChild(self, path, req):
188 if path == "force":
189 return self.force(req)
190 if path == "ping":
191 return self.ping(req)
192 if path == "events":
193 num = req.postpath.pop(0)
194 req.prepath.append(num)
195 num = int(num)
196 # TODO: is this dead code? .statusbag doesn't exist,right?
197 log.msg("getChild['path']: %s" % req.uri)
198 return NoResource("events are unavailable until code gets fixed")
199 filename = req.postpath.pop(0)
200 req.prepath.append(filename)
201 e = self.builder_status.getEventNumbered(num)
202 if not e:
203 return NoResource("No such event '%d'" % num)
204 file = e.files.get(filename, None)
205 if file == None:
206 return NoResource("No such file '%s'" % filename)
207 if type(file) == type(""):
208 if file[:6] in ("<HTML>", "<html>"):
209 return static.Data(file, "text/html")
210 return static.Data(file, "text/plain")
211 return file
212 if path == "builds":
213 return BuildsResource(self.builder_status, self.builder_control)
215 return HtmlResource.getChild(self, path, req)
218 class BuildersResource(HtmlResource):
219 addSlash = True
221 def getChild(self, path, req):
222 s = self.getStatus(req)
223 if path in s.getBuilderNames():
224 builder_status = s.getBuilder(path)
225 builder_control = None
226 c = self.getControl(req)
227 if c:
228 builder_control = c.getBuilder(path)
229 return StatusResourceBuilder(builder_status, builder_control)
231 return HtmlResource.getChild(self, path, req)