| OLD | NEW |
| (Empty) | |
| 1 |
| 2 from twisted.web import html |
| 3 from twisted.web.util import Redirect, DeferredResource |
| 4 from twisted.internet import defer, reactor |
| 5 |
| 6 import urllib, time |
| 7 from twisted.python import log |
| 8 from buildbot.status.web.base import HtmlResource, make_row, make_stop_form, \ |
| 9 css_classes, path_to_builder |
| 10 |
| 11 from buildbot.status.web.tests import TestsResource |
| 12 from buildbot.status.web.step import StepsResource |
| 13 from buildbot import version, util |
| 14 |
| 15 # /builders/$builder/builds/$buildnum |
| 16 class StatusResourceBuild(HtmlResource): |
| 17 addSlash = True |
| 18 |
| 19 def __init__(self, build_status, build_control, builder_control): |
| 20 HtmlResource.__init__(self) |
| 21 self.build_status = build_status |
| 22 self.build_control = build_control |
| 23 self.builder_control = builder_control |
| 24 |
| 25 def getTitle(self, request): |
| 26 return ("Buildbot: %s Build #%d" % |
| 27 (html.escape(self.build_status.getBuilder().getName()), |
| 28 self.build_status.getNumber())) |
| 29 |
| 30 def body(self, req): |
| 31 b = self.build_status |
| 32 status = self.getStatus(req) |
| 33 projectURL = status.getProjectURL() |
| 34 projectName = status.getProjectName() |
| 35 data = ('<div class="title"><a href="%s">%s</a></div>\n' |
| 36 % (self.path_to_root(req), projectName)) |
| 37 # the color in the following line gives python-mode trouble |
| 38 builder_name = b.getBuilder().getName() |
| 39 data += ("<h1><a href=\"%s\">Builder %s</a>: Build #%d</h1>\n" |
| 40 % (path_to_builder(req, b.getBuilder()), |
| 41 builder_name, b.getNumber())) |
| 42 |
| 43 if not b.isFinished(): |
| 44 data += "<h2>Build In Progress</h2>" |
| 45 when = b.getETA() |
| 46 if when is not None: |
| 47 when_time = time.strftime("%H:%M:%S", |
| 48 time.localtime(time.time() + when)) |
| 49 data += "<div>ETA %ds (%s)</div>\n" % (when, when_time) |
| 50 |
| 51 if self.build_control is not None: |
| 52 stopURL = urllib.quote(req.childLink("stop")) |
| 53 data += make_stop_form(stopURL) |
| 54 |
| 55 if b.isFinished(): |
| 56 results = b.getResults() |
| 57 data += "<h2>Results:</h2>\n" |
| 58 text = " ".join(b.getText()) |
| 59 data += '<span class="%s">%s</span>\n' % (css_classes[results], |
| 60 text) |
| 61 if b.getTestResults(): |
| 62 url = req.childLink("tests") |
| 63 data += "<h3><a href=\"%s\">test results</a></h3>\n" % url |
| 64 |
| 65 ss = b.getSourceStamp() |
| 66 data += "<h2>SourceStamp:</h2>\n" |
| 67 data += " <ul>\n" |
| 68 if ss.branch: |
| 69 data += " <li>Branch: %s</li>\n" % html.escape(ss.branch) |
| 70 if ss.revision: |
| 71 data += " <li>Revision: %s</li>\n" % html.escape(str(ss.revision)) |
| 72 if ss.patch: |
| 73 data += " <li>Patch: YES</li>\n" # TODO: provide link to .diff |
| 74 if ss.changes: |
| 75 data += " <li>Changes: see below</li>\n" |
| 76 if (ss.branch is None and ss.revision is None and ss.patch is None |
| 77 and not ss.changes): |
| 78 data += " <li>build of most recent revision</li>\n" |
| 79 got_revision = None |
| 80 try: |
| 81 got_revision = b.getProperty("got_revision") |
| 82 except KeyError: |
| 83 pass |
| 84 if got_revision: |
| 85 got_revision = str(got_revision) |
| 86 if len(got_revision) > 40: |
| 87 got_revision = "[revision string too long]" |
| 88 data += " <li>Got Revision: %s</li>\n" % got_revision |
| 89 data += " </ul>\n" |
| 90 |
| 91 # TODO: turn this into a table, or some other sort of definition-list |
| 92 # that doesn't take up quite so much vertical space |
| 93 data += "<h2>Buildslave:</h2>\n %s\n" % html.escape(b.getSlavename()) |
| 94 data += "<h2>Reason:</h2>\n%s\n" % html.escape(b.getReason()) |
| 95 |
| 96 data += "<h2>Steps and Logfiles:</h2>\n" |
| 97 # TODO: |
| 98 # urls = self.original.getURLs() |
| 99 # ex_url_class = "BuildStep external" |
| 100 # for name, target in urls.items(): |
| 101 # text.append('[<a href="%s" class="%s">%s</a>]' % |
| 102 # (target, ex_url_class, html.escape(name))) |
| 103 if b.getLogs(): |
| 104 data += "<ol>\n" |
| 105 for s in b.getSteps(): |
| 106 name = s.getName() |
| 107 data += (" <li><a href=\"%s\">%s</a> [%s]\n" |
| 108 % (req.childLink("steps/%s" % urllib.quote(name)), |
| 109 name, |
| 110 " ".join(s.getText()))) |
| 111 if s.getLogs(): |
| 112 data += " <ol>\n" |
| 113 for logfile in s.getLogs(): |
| 114 logname = logfile.getName() |
| 115 logurl = req.childLink("steps/%s/logs/%s" % |
| 116 (urllib.quote(name), |
| 117 urllib.quote(logname))) |
| 118 data += (" <li><a href=\"%s\">%s</a></li>\n" % |
| 119 (logurl, logfile.getName())) |
| 120 data += " </ol>\n" |
| 121 data += " </li>\n" |
| 122 data += "</ol>\n" |
| 123 |
| 124 data += "<h2>Build Properties:</h2>\n" |
| 125 data += "<table><tr><th valign=\"left\">Name</th><th valign=\"left\">Val
ue</th><th valign=\"left\">Source</th></tr>\n" |
| 126 for name, value, source in b.getProperties().asList(): |
| 127 value = str(value) |
| 128 if len(value) > 500: |
| 129 value = value[:500] + " .. [property value too long]" |
| 130 data += "<tr>" |
| 131 data += "<td>%s</td>" % html.escape(name) |
| 132 data += "<td>%s</td>" % html.escape(value) |
| 133 data += "<td>%s</td>" % html.escape(source) |
| 134 data += "</tr>\n" |
| 135 data += "</table>" |
| 136 |
| 137 data += "<h2>Blamelist:</h2>\n" |
| 138 if list(b.getResponsibleUsers()): |
| 139 data += " <ol>\n" |
| 140 for who in b.getResponsibleUsers(): |
| 141 data += " <li>%s</li>\n" % html.escape(who) |
| 142 data += " </ol>\n" |
| 143 else: |
| 144 data += "<div>no responsible users</div>\n" |
| 145 |
| 146 if ss.changes: |
| 147 data += "<h2>All Changes</h2>\n" |
| 148 data += "<ol>\n" |
| 149 for c in ss.changes: |
| 150 data += "<li>" + c.asHTML() + "</li>\n" |
| 151 data += "</ol>\n" |
| 152 #data += html.PRE(b.changesText()) # TODO |
| 153 |
| 154 if b.isFinished() and self.builder_control is not None: |
| 155 data += "<h3>Resubmit Build:</h3>\n" |
| 156 # can we rebuild it exactly? |
| 157 exactly = (ss.revision is not None) or b.getChanges() |
| 158 if exactly: |
| 159 data += ("<p>This tree was built from a specific set of \n" |
| 160 "source files, and can be rebuilt exactly</p>\n") |
| 161 else: |
| 162 data += ("<p>This tree was built from the most recent " |
| 163 "revision") |
| 164 if ss.branch: |
| 165 data += " (along some branch)" |
| 166 data += (" and thus it might not be possible to rebuild it \n" |
| 167 "exactly. Any changes that have been committed \n" |
| 168 "after this build was started <b>will</b> be \n" |
| 169 "included in a rebuild.</p>\n") |
| 170 rebuildURL = urllib.quote(req.childLink("rebuild")) |
| 171 data += ('<form action="%s" class="command rebuild">\n' |
| 172 % rebuildURL) |
| 173 data += make_row("Your name:", |
| 174 "<input type='text' name='username' />") |
| 175 data += make_row("Reason for re-running build:", |
| 176 "<input type='text' name='comments' />") |
| 177 data += '<input type="submit" value="Rebuild" />\n' |
| 178 data += '</form>\n' |
| 179 |
| 180 # TODO: this stuff should be generated by a template of some sort |
| 181 data += '<hr /><div class="footer">\n' |
| 182 |
| 183 welcomeurl = self.path_to_root(req) + "index.html" |
| 184 data += '[<a href="%s">welcome</a>]\n' % welcomeurl |
| 185 data += "<br />\n" |
| 186 |
| 187 data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>' |
| 188 data += "-%s " % version |
| 189 if projectName: |
| 190 data += "working for the " |
| 191 if projectURL: |
| 192 data += "<a href=\"%s\">%s</a> project." % (projectURL, |
| 193 projectName) |
| 194 else: |
| 195 data += "%s project." % projectName |
| 196 data += "<br />\n" |
| 197 data += ("Page built: " + |
| 198 time.strftime("%a %d %b %Y %H:%M:%S", |
| 199 time.localtime(util.now())) |
| 200 + "\n") |
| 201 data += '</div>\n' |
| 202 |
| 203 return data |
| 204 |
| 205 def stop(self, req): |
| 206 b = self.build_status |
| 207 c = self.build_control |
| 208 log.msg("web stopBuild of build %s:%s" % \ |
| 209 (b.getBuilder().getName(), b.getNumber())) |
| 210 name = req.args.get("username", ["<unknown>"])[0] |
| 211 comments = req.args.get("comments", ["<no reason specified>"])[0] |
| 212 reason = ("The web-page 'stop build' button was pressed by " |
| 213 "'%s': %s\n" % (name, comments)) |
| 214 c.stopBuild(reason) |
| 215 # we're at http://localhost:8080/svn-hello/builds/5/stop?[args] and |
| 216 # we want to go to: http://localhost:8080/svn-hello |
| 217 r = Redirect("../..") |
| 218 d = defer.Deferred() |
| 219 reactor.callLater(1, d.callback, r) |
| 220 return DeferredResource(d) |
| 221 |
| 222 def rebuild(self, req): |
| 223 b = self.build_status |
| 224 bc = self.builder_control |
| 225 builder_name = b.getBuilder().getName() |
| 226 log.msg("web rebuild of build %s:%s" % (builder_name, b.getNumber())) |
| 227 name = req.args.get("username", ["<unknown>"])[0] |
| 228 comments = req.args.get("comments", ["<no reason specified>"])[0] |
| 229 reason = ("The web-page 'rebuild' button was pressed by " |
| 230 "'%s': %s\n" % (name, comments)) |
| 231 if not bc or not b.isFinished(): |
| 232 log.msg("could not rebuild: bc=%s, isFinished=%s" |
| 233 % (bc, b.isFinished())) |
| 234 # TODO: indicate an error |
| 235 else: |
| 236 bc.resubmitBuild(b, reason) |
| 237 # we're at |
| 238 # http://localhost:8080/builders/NAME/builds/5/rebuild?[args] |
| 239 # Where should we send them? |
| 240 # |
| 241 # Ideally it would be to the per-build page that they just started, |
| 242 # but we don't know the build number for it yet (besides, it might |
| 243 # have to wait for a current build to finish). The next-most |
| 244 # preferred place is somewhere that the user can see tangible |
| 245 # evidence of their build starting (or to see the reason that it |
| 246 # didn't start). This should be the Builder page. |
| 247 r = Redirect("../..") # the Builder's page |
| 248 d = defer.Deferred() |
| 249 reactor.callLater(1, d.callback, r) |
| 250 return DeferredResource(d) |
| 251 |
| 252 def getChild(self, path, req): |
| 253 if path == "stop": |
| 254 return self.stop(req) |
| 255 if path == "rebuild": |
| 256 return self.rebuild(req) |
| 257 if path == "steps": |
| 258 return StepsResource(self.build_status) |
| 259 if path == "tests": |
| 260 return TestsResource(self.build_status) |
| 261 |
| 262 return HtmlResource.getChild(self, path, req) |
| 263 |
| 264 # /builders/$builder/builds |
| 265 class BuildsResource(HtmlResource): |
| 266 addSlash = True |
| 267 |
| 268 def __init__(self, builder_status, builder_control): |
| 269 HtmlResource.__init__(self) |
| 270 self.builder_status = builder_status |
| 271 self.builder_control = builder_control |
| 272 |
| 273 def getChild(self, path, req): |
| 274 try: |
| 275 num = int(path) |
| 276 except ValueError: |
| 277 num = None |
| 278 if num is not None: |
| 279 build_status = self.builder_status.getBuild(num) |
| 280 if build_status: |
| 281 if self.builder_control: |
| 282 build_control = self.builder_control.getBuild(num) |
| 283 else: |
| 284 build_control = None |
| 285 return StatusResourceBuild(build_status, build_control, |
| 286 self.builder_control) |
| 287 |
| 288 return HtmlResource.getChild(self, path, req) |
| 289 |
| OLD | NEW |