Commits

Dan Boitnott committed 9d44556

Output viewing works

Comments (0)

Files changed (9)

         bodyEnd = len(lines) - 1
         for i in range(bodyEnd, bodyStart, -1):
             line = lines[i]
+            if len(line) < 1:
+                # Empty line
+                continue
+
             if line[0] == '=':
                 bodyEnd = i
                 break
-            (key, value) = line.split(':', 1)
-            self.headers[key.strip()] = value.strip()
+            try:
+                (key, value) = line.split(':', 1)
+                self.headers[key.strip()] = value.strip()
+            except:
+                # Not a valid header line. Output might not be finished
+                break
 
         # Remove trailing blank lines
         while bodyEnd > bodyStart and lines[bodyEnd-1].strip() == '':

src/resources/static/back-icon.png

Added
New image

src/resources/static/ellipsis-icon.png

Added
New image

src/resources/static/global.css

     position: absolute;
     top: 3px;
     right: 3px;
-}
+}
+
+.outputToolbar {
+    position: absolute;
+    top: 15px;
+    right: 15px;
+}
+
+.outputHeaders {
+    border-bottom: 1px dashed black;
+    width: 100%;
+    background-color: #b8b8b8;
+}
+
+.outputHeaders th {
+    text-align: right;
+    padding-right: 3px;
+}
+
+.outputHeaders td {
+    width: 100%;
+}
+

src/resources/templates/index.mustache

         <pre>{{lines}}</pre>
         {{#isCommandTask}}
             {{#hasOutput}}
+                {{#outputIsTail}}
+                    <a href="showOutput?key={{hash}}.{{lastOutputTs}}">
+                        <img src="static/ellipsis-icon.png" alt="Show More" title="Show More" style="margin-top: 4px;"/>
+                    </a>
+                {{/outputIsTail}}
                 <pre class="taskOutput">{{lastOutputTail}}</pre>
-                <form class="viewOutputForm">
+                <form class="viewOutputForm" method="get" action="showOutput">
                     Output:
-                    <select>
+                    <select name="key">
                         {{#output}}<option value="{{hash}}.{{ts}}">{{ts}}</option>{{/output}}
                     </select>
-                    <input type="submit" name="action" value="View"/>
+                    <input type="submit" value="View"/>
                 </form>
             {{/hasOutput}}
         {{/isCommandTask}}

src/resources/templates/showOutput.mustache

+{{>stdhead}}
+
+<div class="outputToolbar">
+    <a href="/"><img src="static/back-icon.png" alt="Go Back" title="Go Back"/></a>
+</div>
+
+<table class="outputHeaders">
+    <tr><th>Command:</th><td><span style="font-family:monospace">{{headers.Command}}</span></td></tr>
+    <tr><th>Host:</th><td><span style="font-family:monospace">{{headers.Host}}</span></td></tr>
+    <tr><th>Started:</th><td>{{headers.Started}}</td></tr>
+    <tr><th>Finished:</th><td>{{headers.Finished}}</td></tr>
+</table>
+<div class="outputBody">
+    <pre>{{body}}</pre>
+</div>
+
+{{>stdfoot}}
             curCmdLog.close()
             curCmdLog = None
 
+def getCommandOutputPathForKey(key):
+    ret = os.path.join(runDir, "cmd.%s.log" % key)
+    if not util.isSubfile(runDir, ret):
+        return None
+    return ret
+
 def getCommandOutputPath(cmd, ts):
-    return os.path.join(runDir, "cmd.%s.%s.log" % (util.cmdHash(cmd.strip()), ts))
+    return getCommandOutputPathForKey(util.cmdHash(cmd.strip()) + "." + ts)
 
 def logCommand(cmd):
     with curCmdLogLock:
 
     return output.CommandOutput(getCommandOutputPath(cmd, ts))
 
+def getCommandOutputForKey(key):
+    return output.CommandOutput(getCommandOutputPathForKey(key))
+
 def printToUser(msg, reprompt = True):
     # Print to STDERR so that it won't be sent to the shell
     print >>sys.stderr, "\r\n", msg.replace("\n", "\n\r"), "\r"
 import time
 import hashlib
+import os.path
 
 RULE = "=" * 80
 
     for k in dict:
         print >>fp, k + ":", dict[k]
     print >>fp, RULE
+
+def isSubfile(dirPath, filePath):
+    return os.path.commonprefix([os.path.abspath(dirPath), os.path.abspath(filePath)]) == dirPath
 
     # Ensure that the path is under the correct folder. This is to thwart malicious URLs that might contain extra
     # slashes or '..'
-    if os.path.commonprefix([dirPath, filePath]) != dirPath:
+    if not util.isSubfile(dirPath, filePath):
         return None
 
     if not os.path.isfile(filePath):
         return None
 
 class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
-    renderer = pystache.Renderer(search_dirs=[resourcePath(TEMPLATES)], missing_tags='strict')
+    renderer = pystache.Renderer(search_dirs=[resourcePath(TEMPLATES)], missing_tags='ignore')
 
     def log_message(self, format, *args):
         self.server.logFile.write("%s - - [%s] %s\n" %
                                   format%args))
 
     def do_GET(self):
-        if self.path == '/':
+        urlParts = urlparse.urlparse(self.path)
+
+        if urlParts.path == '/':
             (cmd, subPath) = ("index", '')
         else:
-            (head, tail) = os.path.split(self.path.lstrip('/'))
+            (head, tail) = os.path.split(urlParts.path.lstrip('/'))
             if head == 'static':
                 self.sendStatic(tail)
                 return
             return
 
         method = getattr(self, methodName)
-        parts = urlparse.urlsplit(subPath)
 
         try:
-            method(parts.path, urlparse.parse_qs(parts.query))
+            method(subPath, urlparse.parse_qs(urlParts.query))
         except KeyboardInterrupt as ex:
             raise ex
         except:
         self.end_headers()
         self.wfile.write("1")
 
+    def cmd_showOutput(self, path, query):
+        key = query['key'][0]
+        output = state.getCommandOutputForKey(key)
+        self.sendTemplate("showOutput", headers=output.headers, body=output.getBody())
+
     def cmd_index(self, path, query):
         self.log_message
         tasks = []
                 colorClass = '' # Comments are all colored the same
 
             if len(times) > 0:
-                lastOutput = state.getCommandOutput(task.content.strip(), times[0])
+                lastOutputTs = times[0]
+                lastOutput = state.getCommandOutput(task.content.strip(), lastOutputTs)
                 lastOutputTail = lastOutput.tail(TAIL_LINES)
                 outputIsTail = len(lastOutput.bodyLines) > TAIL_LINES
             else:
                 lastOutputTail = ''
+                lastOutputTs = ''
                 outputIsTail = False
 
             tasks.append({
                 "output"        : output,
                 "hasOutput"     : len(output) > 0,
                 "lastOutputTail": lastOutputTail,
+                "lastOutputTs"  : lastOutputTs,
                 "outputIsTail"  : outputIsTail
             })