b8e0ec67d7da42dbdd9837bb11833cdc6b761b31
hiram
  Mon Jun 8 14:50:45 2026 -0700
eliminate the checkIp and reset STATUS_NAMES to liftStatus refs #31811

diff --git src/hg/utils/otto/userRequests/ottoRequestView.cgi src/hg/utils/otto/userRequests/ottoRequestView.cgi
index 9f15674425b..3495e163221 100644
--- src/hg/utils/otto/userRequests/ottoRequestView.cgi
+++ src/hg/utils/otto/userRequests/ottoRequestView.cgi
@@ -7,90 +7,95 @@
 Access is restricted to a single IP (UCSC VPN, 128.114.198.5).
 Any other REMOTE_ADDR gets a 403.
 """
 
 import cgi
 import html
 import json
 import os
 import re
 import subprocess
 import sys
 import time
 import urllib.parse
 from datetime import datetime
 
-ALLOWED_IP   = '128.114.198.5'
 TRASH        = '/data/apache/trash'
 TABLE        = 'ottoRequest'
 
 # Configuration will be set dynamically in main()
 HGDB_CONF = None
 DB = None
 USE_PROFILE = None
 
 # Galaxy queue status panel - snapshot is refreshed by ottoRequestWatch.sh
 # (cron, every 11 minutes), CGI just reads it.
 CACHE_PATH = '/data/apache/trash/ottoRequestGalaxyStatus.json'
 CACHE_TTL  = 1800  # seconds; older than this -> show "stale" instead
 
 # featureBits coverage snapshot - append-only file maintained by
 # featureBitsSnapshot.py (cron, via ottoRequestWatch.sh).  fb.*.txt
 # values are immutable once an alignment completes so no TTL is needed;
 # featureBitsPct() falls back to an NFS read on a snapshot miss.
 FB_SNAPSHOT_PATH = '/data/apache/trash/ottoRequestFeatureBitsPct.json'
 
-# from README.txt in this directory
-STATUS_NAMES = {
+liftStatus = {
     0: 'received by API',
     1: 'acknowledged, email sent',
     2: 'galaxy job started',
     3: 'galaxy done, download started',
     4: 'downloaded, track files made',
     5: 'symlinks ready, awaiting push',
     6: 'push complete',
     7: 'ERROR',
     8: 'COMPLETE (final email sent)',
 }
 
+asmStatus = {
+    0: 'received by API',
+    1: 'acknowledged, email sent',
+    2: 'assembly build started',
+    3: 'assembly build done',
+    4: 'downloaded, track files made',
+    5: 'symlinks ready, awaiting push',
+    6: 'push complete',
+    7: 'ERROR',
+    8: 'COMPLETE (final email sent)',
+}
+
+
 COLS = ['id', 'requestType', 'fromDb', 'toDb', 'email', 'comment',
         'requestTime', 'status', 'buildDir', 'completeTime']
 
 # featureBits coverage lookup roots
 HIVE_GENOMES = '/hive/data/genomes'
 ASMHUB_ROOT  = HIVE_GENOMES + '/asmHubs'
 
 # in-process caches; one CGI invocation only, but rows reuse same accessions
 _buildDirCache = {}
 _fbPctCache    = {}
 _genarkAsmName = {}    # populated up-front by loadGenarkNames()
 _fbSnapshot    = {}    # populated up-front by loadFeatureBitsSnapshot()
 
 
 def forbidden(msg):
     sys.stdout.write("Status: 403 Forbidden\r\n")
     sys.stdout.write("Content-Type: text/plain; charset=utf-8\r\n\r\n")
     sys.stdout.write(msg + "\n")
     sys.exit(0)
 
 
-def checkIp():
-    remote = os.environ.get('REMOTE_ADDR', '')
-    if remote != ALLOWED_IP:
-        forbidden(f"Access denied for {remote!r}; this page is restricted.")
-
-
 def setDbConfig(use_otto=False):
     """Set database configuration globals based on config parameter."""
     global HGDB_CONF, DB, USE_PROFILE
     if use_otto:
         HGDB_CONF = '/data/apache/cgi-bin/otto/.otto.conf'
         DB = 'hgcentral'
         USE_PROFILE = False
     else:
         HGDB_CONF = '/usr/local/apache/cgi-bin/hg.conf'
         DB = 'hgcentraltest'
         USE_PROFILE = True
 
 
 def unescapeMysql(s):
     """Reverse `hgsql -B` escaping (\\n, \\t, \\\\, \\0). One pass so
@@ -130,39 +135,39 @@
     ok, out, err = hgsqlRun(sql)
     if not ok:
         raise RuntimeError(err.strip() or 'hgsql failed')
     rows = []
     if out.strip():
         for line in out.rstrip('\n').split('\n'):
             rows.append([unescapeMysql(f) for f in line.split('\t')])
     return rows
 
 
 def doResetStatus(form):
     rid  = form.getfirst('id', '')
     stat = form.getfirst('status', '')
     if not rid.isdigit():
         return None, f"bad id: {rid!r}"
-    if not stat.isdigit() or int(stat) not in STATUS_NAMES:
+    if not stat.isdigit() or int(stat) not in liftStatus:
         return None, f"bad status: {stat!r}"
     sql = (f"UPDATE {TABLE} SET status = {int(stat)} "
            f"WHERE id = {int(rid)}")
     ok, _out, err = hgsqlRun(sql)
     if not ok:
         return None, err.strip() or 'hgsql update failed'
     return (f"id={rid} status set to {stat} "
-            f"({STATUS_NAMES[int(stat)]})"), None
+            f"({liftStatus[int(stat)]})"), None
 
 
 def loadGenarkNames(accessions):
     """Populate _genarkAsmName: {gcAccession: asmName} for the given
     accessions in one bulk hgsql call against the genark table.  Lets
     hubBuildDir() construct paths directly instead of NFS-listdir'ing
     /hive/data/genomes/asmHubs/.../<XXX>/<XXX>/<XXX>/ to discover the
     asmName suffix on each accession."""
     if not accessions:
         return
     quoted = ",".join("'%s'" % a for a in sorted(accessions))
     sql = (f"SELECT gcAccession, asmName FROM genark "
            f"WHERE gcAccession IN ({quoted});")
     ok, out, _err = hgsqlRun(sql)
     if not ok or not out.strip():
@@ -339,31 +344,31 @@
             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()))
+                          for k, v in liftStatus.items()))
     # Count rows by type for toggle button labels
     completed_count = sum(1 for r in rows if len(r) > 7 and r[7] == '8')
     assembly_count = sum(1 for r in rows if len(r) > 1 and r[1] == 'assembly')
     liftover_count = sum(1 for r in rows if len(r) > 1 and r[1] == 'liftOver')
     # Config toggle button - switches between test and production databases
     current_config = 'otto' if use_otto else 'test'
     switch_config = 'test' if use_otto else 'otto'
     switch_label = 'Switch to hgwdev' if use_otto else 'Switch to RR'
     config_url = f'?config={switch_config}'
 
     out(f' &middot; <b>{len(rows)}</b> row(s)'
         f'<a href="{config_url}" class="configBtn">{switch_label}</a>'
         '<button class="refreshBtn" type="button" '
         'onclick="location.reload()">refresh</button>'
         f'<button class="toggleBtn" type="button" id="toggleComplete" '
@@ -397,31 +402,31 @@
         cls_parts = []
         if stnum in (7, 8):
             cls_parts.append(f's{stnum}')
         # Add requestType class for toggle filtering
         if typeIdx < len(r):
             req_type = r[typeIdx].lower()
             if req_type in ('assembly', 'liftover'):
                 cls_parts.append(req_type)
         cls = ' '.join(cls_parts)
         out(f'<tr class="{cls}">')
         for i, c in enumerate(COLS):
             cell = r[i] if i < len(r) else ''
             if c == 'comment':
                 out(f'<td class="comment">{html.escape(cell)}</td>')
             elif c == 'status':
-                label = STATUS_NAMES.get(stnum, '?')
+                label = liftStatus.get(stnum, '?')
                 out(f'<td><b>{html.escape(cell)}</b> '
                     f'<small>{html.escape(label)}</small></td>')
             elif c in ('fromDb', 'toDb') and cell:
                 href = ('https://genome-test.gi.ucsc.edu/cgi-bin/hgTracks?db='
                         + urllib.parse.quote(cell, safe=''))
                 out(f'<td><a href="{html.escape(href)}" target="_blank">'
                     f'{html.escape(cell)}</a></td>')
             elif c == 'email' and '@' in cell:
                 user = cell.split('@', 1)[0]
                 out(f'<td title="{html.escape(cell)}">'
                     f'{html.escape(user)}</td>')
             else:
                 out(f'<td>{html.escape(cell)}</td>')
         fromAcc = r[fromIdx] if fromIdx < len(r) else ''
         toAcc   = r[toIdx]   if toIdx   < len(r) else ''
@@ -431,31 +436,31 @@
             out(f'<td>{html.escape(fwd or "-")} / '
                 f'{html.escape(rev or "-")}</td>')
         else:
             out('<td></td>')
 
         elapsed = elapsedStr(r[reqIdx]  if reqIdx  < len(r) else '',
                              r[doneIdx] if doneIdx < len(r) else '')
         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):
+        for k in sorted(liftStatus):
             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'
@@ -504,31 +509,30 @@
         '    if (localStorage.getItem("hideAssembly") === "true") {\n'
         '        var btn = document.getElementById("toggleAssembly");\n'
         '        table.classList.add("hide-assembly");\n'
         '        btn.textContent = "show assembly";\n'
         '    }\n'
         '    if (localStorage.getItem("hideLiftover") === "true") {\n'
         '        var btn = document.getElementById("toggleLiftover");\n'
         '        table.classList.add("hide-liftover");\n'
         '        btn.textContent = "show liftOver";\n'
         '    }\n'
         '});\n'
         '</script>\n')
     out('</body></html>\n')
 
 def main():
-#   checkIp()
 
     # Create FieldStorage once - it consumes stdin and can't be read twice
     form = cgi.FieldStorage()
 
     # Detect configuration from URL parameter
     use_otto = form.getfirst('config') == 'otto'
     setDbConfig(use_otto)
 
     # 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':
         action = form.getfirst('action', '')
         if action == 'resetStatus':
             info, error = doResetStatus(form)
         else: