9e84b6fc13d60a1beb638f18050cb1fed5835410
hiram
  Sun May 24 12:04:05 2026 -0700
add a "hide/show completed" toggle and no longer need to protect via VPN IP with .htaccess now refs #31811

diff --git src/hg/utils/otto/userRequests/ottoRequestView.cgi src/hg/utils/otto/userRequests/ottoRequestView.cgi
index 0a81e3b954f..dc7d51122da 100644
--- src/hg/utils/otto/userRequests/ottoRequestView.cgi
+++ src/hg/utils/otto/userRequests/ottoRequestView.cgi
@@ -289,55 +289,62 @@
         'font-size:12px}\n'
         'th{background:#eee;text-align:left;position:sticky;top:0}\n'
         'tr:nth-child(even){background:#f8f8f8}\n'
         'td.comment{max-width:28em;white-space:pre-wrap;'
         'word-break:break-word}\n'
         'tr.s7 td{background:#ffe0e0}\n'
         'tr.s8 td{background:#e0f0e0;color:#555}\n'
         'select,button{font-size:12px}\n'
         '.banner{padding:.5em;margin:.4em 0;border-radius:4px}\n'
         '.info {background:#dfd;border:1px solid #5a5}\n'
         '.error{background:#fdd;border:1px solid #a55}\n'
         '.legend{font-size:15px;color:#333;margin:.4em 0}\n'
         '.legend code{background:#eee;padding:0 3px;font-size:14px}\n'
         '.refreshBtn{font-size:14px;padding:3px 10px;margin-left:6px;'
         'cursor:pointer}\n'
+        '.toggleBtn{font-size:14px;padding:3px 10px;margin-left:6px;'
+        'cursor:pointer;background:#f0f0f0;border:1px solid #ccc}\n'
+        '.hide-complete tr.s8{display:none}\n'
         '</style></head><body>\n')
 
     out(f'<h2>{DB}.{TABLE}</h2>\n')
     if galaxyStatus:
         staleNote = ' <b>[stale]</b>' if galaxyStatus.get('stale') else ''
         out('<div class="legend">Galaxy queue: '
             f'<b>{galaxyStatus.get("running", "?")}</b> running &middot; '
             f'<b>{galaxyStatus.get("queued",  "?")}</b> queued &middot; '
             f'<b>{galaxyStatus.get("new",     "?")}</b> new '
             f'<small>(as of {html.escape(galaxyStatus.get("ts", ""))})</small>'
             f'{staleNote}</div>\n')
     else:
         out('<div class="legend">Galaxy queue: '
             '<i>status unavailable</i></div>\n')
     if info:
         out(f'<div class="banner info">{html.escape(info)}</div>\n')
     if error:
         out(f'<div class="banner error">{html.escape(error)}</div>\n')
 
     out('<div class="legend">status: ')
     out(' &middot; '.join(f'<code>{k}</code>={html.escape(v)}'
                           for k, v in STATUS_NAMES.items()))
+    # Count completed rows for the toggle button label
+    completed_count = sum(1 for r in rows if len(r) > 7 and r[7] == '8')
     out(f' &middot; <b>{len(rows)}</b> row(s)'
         '<button class="refreshBtn" type="button" '
-        'onclick="location.reload()">refresh</button></div>\n')
+        'onclick="location.reload()">refresh</button>'
+        f'<button class="toggleBtn" type="button" id="toggleComplete" '
+        f'onclick="toggleCompleted()">hide completed ({completed_count})</button></div>\n')
     out(f'<div class="legend">cron times: 9,20,31,42,53 for ottoRequestWatch.sh, and 4,26,46 for ottoRequestPush and 1,8,15,22,29,36,43,50,57 for the first acknowledgement</div>\n')
 
     out('<table class="sortable">\n<tr>')
     for c in COLS:
         out(f'<th>{c}</th>')
     out('<th title="% of fromDb covered by chains to toDb / '
         '% of toDb covered by chains to fromDb">'
         'coverage<br><small>from / to</small></th>'
         '<th>elapsed</th><th>set status</th></tr>\n')
 
     reqIdx  = COLS.index('requestTime')
     doneIdx = COLS.index('completeTime')
     fromIdx = COLS.index('fromDb')
     toIdx   = COLS.index('toDb')
 
@@ -383,34 +390,59 @@
         out(f'<td>{html.escape(elapsed)}</td>')
         # reset form
         out('<td><form method="post" '
             'onsubmit="return confirm(\'Reset status of id=' +
             html.escape(rid) + ' to \' + this.status.value + \'?\')">'
             '<input type="hidden" name="action" value="resetStatus">'
             f'<input type="hidden" name="id" value="{html.escape(rid)}">'
             '<select name="status">')
         for k in sorted(STATUS_NAMES):
             sel = ' selected' if k == stnum else ''
             out(f'<option value="{k}"{sel}>{k}</option>')
         out('</select> <button type="submit">set</button></form></td>')
         out('</tr>\n')
     out('</table>\n')
     out('<script src="/js/sorttable.js"></script>\n')
+    out('<script>\n'
+        'function toggleCompleted() {\n'
+        '    var table = document.querySelector("table");\n'
+        '    var btn = document.getElementById("toggleComplete");\n'
+        '    var isHidden = table.classList.contains("hide-complete");\n'
+        '    if (isHidden) {\n'
+        '        table.classList.remove("hide-complete");\n'
+        f'        btn.textContent = "hide completed ({completed_count})";\n'
+        '        localStorage.setItem("hideCompleted", "false");\n'
+        '    } else {\n'
+        '        table.classList.add("hide-complete");\n'
+        '        btn.textContent = "show completed";\n'
+        '        localStorage.setItem("hideCompleted", "true");\n'
+        '    }\n'
+        '}\n'
+        '// Restore toggle state from localStorage\n'
+        'window.addEventListener("load", function() {\n'
+        '    if (localStorage.getItem("hideCompleted") === "true") {\n'
+        '        var table = document.querySelector("table");\n'
+        '        var btn = document.getElementById("toggleComplete");\n'
+        '        table.classList.add("hide-complete");\n'
+        '        btn.textContent = "show completed";\n'
+        '    }\n'
+        '});\n'
+        '</script>\n')
     out('</body></html>\n')
 
 def main():
-    checkIp()
+#   checkIp()
 
     # POST/Redirect/GET: handle the write, then 303 to a GET of the same URL
     # so a browser reload doesn't re-submit the form and re-run the UPDATE.
     if os.environ.get('REQUEST_METHOD', 'GET') == 'POST':
         form = cgi.FieldStorage()
         action = form.getfirst('action', '')
         if action == 'resetStatus':
             info, error = doResetStatus(form)
         else:
             info, error = None, f"unknown action: {action!r}"
         params = {}
         if info:  params['info']  = info
         if error: params['error'] = error
         qs = ('?' + urllib.parse.urlencode(params)) if params else ''
         sys.stdout.write(f"Status: 303 See Other\r\n"