a8f29caea973a2482924c38c981b59a28e22e8ec
max
  Fri Oct 22 05:14:38 2021 -0700
making hgHubConnect more self-explanatory, with more text. The
development tab is revamped to provide a visual indicator of what's
going on while hubCheck is running. (The concept with an IFRAME could well be applied to hgc links
from hgTracks, to avoid a full page reload.), refs #28324

diff --git src/hg/hgHubConnect/hgHubConnect.c src/hg/hgHubConnect/hgHubConnect.c
index 3b9bd21..b483490 100644
--- src/hg/hgHubConnect/hgHubConnect.c
+++ src/hg/hgHubConnect/hgHubConnect.c
@@ -19,30 +19,32 @@
 #include "dbDb.h"
 #include "web.h"
 #include "trackHub.h"
 #include "hubConnect.h"
 #include "dystring.h"
 #include "hPrint.h"
 #include "jsHelper.h"
 #include "obscure.h"
 #include "hgConfig.h"
 #include "trix.h"
 #include "net.h"
 #include "hubSearchText.h"
 #include "pipeline.h"
 #include "hubPublic.h"
 
+static boolean measureTiming;
+
 struct cart *cart;	/* The user's ui state. */
 struct hash *oldVars = NULL;
 
 static char *pageTitle = "Track Data Hubs";
 char *database = NULL;
 char *organism = NULL;
 
 struct hubOutputStructure
     {
     struct hubOutputStructure *next;
     struct dyString *metaTags;
     struct dyString *descriptionMatch;
     struct genomeOutputStructure *genomes;
     int genomeCount;
     struct hash *genomeOutHash;
@@ -210,32 +212,38 @@
     el = slNameNew(genomes->name);
     slAddHead(&list, el);
     }
 slReverse(&list);
 printGenomeList(thub->url, list, row, withLink);
 }
 
 
 static void hgHubConnectUnlisted(struct hubConnectStatus *hubList, 
     struct hash *publicHash)
 /* Put up the list of unlisted hubs and other controls for the page. */
 /* uses publicHash to distingusih public hubs from unlisted ones */
 /* NOTE: Destroys hubList */
 {
 // put out the top of our page
-printf("<div id=\"unlistedHubs\" class=\"hubList\"> \n"
-    "<table id=\"unlistedHubsTable\"> \n"
+puts("<div id=\"unlistedHubs\" class=\"hubList\"> \n"
+    "<div class='tabSection' style='border-bottom:none'>");
+
+printf("<FORM ACTION=\"%s\" id='unlistedHubForm' NAME=\"unlistedHubForm\">\n",  "../cgi-bin/hgGateway");
+cgiMakeHiddenVar(hgHubConnectRemakeTrackHub, "on");
+cartSaveSession(cart);
+
+puts("<table id=\"unlistedHubsTable\"> \n"
     "<thead><tr> \n"
 	"<th colspan=\"6\" id=\"addHubBar\"><label for=\"hubUrl\">URL:</label> \n"
 	"<input name=\"hubText\" id=\"hubUrl\" class=\"hubField\" "
 	    "type=\"text\" size=\"65\"> \n"
 	"<input name=\"hubAddButton\" id='hubAddButton' "
 		"class=\"hubField\" type=\"button\" value=\"Add Hub\">\n"
 	"</th> \n"
     "</tr> \n");
 jsOnEventById("click", "hubAddButton", 
     "var hubText = document.getElementById('hubUrl');"
     "hubText.value=$.trim(hubText.value);"
     "if(validateUrl($('#hubUrl').val())) { "
     " document.addHubForm.elements['hubUrl'].value=hubText.value;"
     " document.addHubForm.submit(); return true; } "
     "else { return false; }"
@@ -247,45 +255,62 @@
 struct hubConnectStatus *hub, *nextHub;
 
 for(hub = hubList; hub; hub = nextHub)
     {
     nextHub = hub->next;
     // if url is not in publicHash, it's unlisted */
     if (!((publicHash != NULL) && hashLookup(publicHash, hub->hubUrl)))
 	{
 	unlistedHubCount++;
 	slAddHead(&unlistedHubList, hub);
 	}
     }
 
 hubList = NULL;  // hubList no longer valid
 
+puts("<p>If have a new hub URL, you can add it here, then connect/disconnect "
+        " as needed. Other users will not see your hub.</p>"
+    "<p>To share your hub with others, connect it below, then save a session "
+        "from the Genome Browser (\"My Data > My Sessions\"). "
+        " You can put the session link "
+        "into a manuscript or send it to others. This means that you can always update the session later. "
+        "Users who click the session link will lose their own Genome Browser "
+        "configuration, connected hubs and custom tracks.</p>"
+        "<p>If you want to avoid overriding their sessions, construct a link for others with "
+        "the hubUrl argument, like this: "
+        "https://genome.ucsc.edu/cgi-bin/hgTracks?db=hg38&hubUrl=URL_TO_HUB"
+        "</p>"
+        "<p><a href=\"../contacts.html\">Contact us</A> if you want to add a hub to the list of public hubs.</p>\n"
+        );
+
 if (unlistedHubCount == 0)
     {
     // nothing to see here
     printf("<tr><td>No Unlisted Track Hubs</td></tr>");
-    printf("</thead></table></div>");
+    printf("</thead></table>");
+    puts("</FORM>");      // return from within DIV and FROM is probably not a good idea
+    puts("</div></div>"); // tabSection and .unlistedHubs
     return;
     }
 
 // time to output the big table.  First the header
-printf(
+puts(
     "<tr> "
 	"<th>Display</th> "
 	"<th>Hub Name</th> "
 	"<th>Description</th> "
-	"<th>Assemblies</th> "
+	"<th>Assemblies<span class='assemblyClickNote'>Click to connect and browse directly</span></th> "
     "</tr>\n"
     "</thead>\n");
 
 // start first row
 printf("<tbody>");
 
 char id[256];
 int count = 0;
 for(hub = unlistedHubList; hub; hub = hub->next)
     {
     char hubName[64];
     safef(hubName, sizeof(hubName), "%s%u", hgHubConnectHubVarPrefix, hub->id);
     if (!cartUsualBoolean(cart, hubName, FALSE))
 	continue;
 
@@ -337,222 +362,204 @@
 	    ourPrintCell(hub->trackHub->longLabel);
 	}
     else
 	ourPrintCell("");
 
 
     if (hub->trackHub != NULL)
 	printGenomes(hub->trackHub, count, !hubHasError);
     else
 	ourPrintCell("");
 
     puts("</tr>");
     }
 
 printf("</tbody></TABLE>\n");
-printf("</div>");
+puts("</FORM>");
+printf("</div>"); // .tabSection
+printf("</div>"); // #unlistedHubs
+
 }
 
 void doValidateNewHub(char *hubUrl)
 /* Run hubCheck on a hub. */
 {
 udcSetCacheTimeout(1);
 printf("<div id=\"validateHubResult\" class=\"hubTdbTree\" style=\"overflow: auto\"></div>");
 char *cmd[] = {"loader/hubCheck", "-htmlOut", "-noTracks", hubUrl, NULL};
 struct pipeline *pl = pipelineOpen1(cmd, pipelineRead | pipelineNoAbort, NULL, NULL);
 struct lineFile *lf = pipelineLineFile(pl);
 char *line;
 while (lineFileNext(lf, &line, NULL))
     jsInlineF("%s", line);
 pipelineClose(&pl);
 // the 'false' below prevents a few hub-search specific jstree configuration options
 jsInline("hubSearchTree.init(false);");
 }
 
 void hgHubConnectDeveloperMode()
 /* Put up the controls for the "Hub Development" Tab, which includes a button to run the
  * hubCheck utility on a hub and load a hub with the udcTimeout and measureTiming
  * variables turned on */
 {
 // put out the top of our page
 char *hubUrl = cartOptionalString(cart, "validateHubUrl");
-boolean doHubValidate = cartVarExists(cart, hgHubDoHubCheck);
 
 // the outer div for all the elements in the tab
-printf("\n<div id=\"hubDeveloper\" class=\"hubList\">\n");
+puts("<div id=\"hubDeveloper\" class=\"hubList\">");
 
-// the row to enter in the url and the button and settings to validate/load
-printf("<div class=\"addHubBar roundCorners\" id=\"validateHubUrlRow\">\n");
-printf("<label for=\"validateHubUrl\"><b>URL:</b></label>\n");
+char *hubUrlVal = "";
 if (hubUrl != NULL)
+    hubUrlVal = catThreeStrings(" value='", hubUrl, "'");
+
+puts("<div class='tabSection'>");
+puts("<h4>Create your own hub</h4>");
+puts("For information on making track hubs, please see the following pages: \n "
+    "<ul>\n"
+    "<li><a href=\"../goldenPath/help/hgTrackHubHelp.html\" TARGET=_blank>Track Hub Users Guide</a></li>\n"
+    "<li><a href='../goldenPath/help/hubQuickStart.html' target=_blank>Quick Start</a></li>\n"
+    "<li><a href=\"../goldenPath/help/trackDb/trackDbHub.html\" target=_blank>Track Hub Settings Reference</a></li>\n"
+    "<li><a href=\"../goldenPath/help/hgTrackHubHelp#Hosting\" target=_blank>Where to host your track hub</a></li>\n"
+    "<li><a href='../contacts.html'>Contact us</a> if you have trouble</li>\n"
+    "</ul>\n"
+    "Example hub URL: http://genome.ucsc.edu/goldenPath/help/examples/hubDirectory/hub.txt");
+puts("</div>"); // .tabSection
+
+puts("<div class='tabSection'>");
+puts("<h4>Check a hub for errors</h4>");
+printf("<label for=\"validateHubUrl\"><b>Hub URL:</b></label>");
+printf("<input id='validateHubUrl' name='validateHubUrl' class='hubField' type='text' size='65'%s>\n", hubUrlVal);
+printf("<button type='button' id='hubValidateButton'>Check</button>\n");
+
+puts("<div class='help'>When debugging a hub, it is sometimes helpful to check it for errors. "
+        "With the button above, you can validate the hub's configuration files, including hub.txt, "
+        "genomes.txt and trackDb.txt. "
+    "A hierarchical tree of tracks is presented with any errors in red. A hub "
+    "with no errors still shows the tree which can be used to explore the track hierarchy. "
+    "Hub error checking will always refresh the files and never use our remote file cache (see below)."
+    "</div>\n "
+);
+puts("</div>"); // .tabSection
+
+puts("<div class='tabSection'>");
+puts("<h4>Enable Genome Browser debugging modes</h4>");
+puts("<div class='help'>These apply to all connected hubs. By default, caching is activated and track load times are not shown, but you can change these settings when debugging your hub.</div>");
+puts("<div style='margin-left: 15px'>");
+puts("<FORM ACTION='hgHubConnect#hubDeveloper' METHOD='POST' NAME='debugForm'>");
+
+// output the udcTimeout button
+char *noCacheLabel = "Deactivate (Always refresh)";
+char *timeout = "5";
+char *cacheStatus = "ON";
+char *description = "Always refresh files, do not cache contents at UCSC. This means that data access is slower, but you can see the effect of changes to your files immediately on the Genome Browser.";
+if (cartNonemptyString(cart, "udcTimeout")) 
     {
-    printf("<input name=\"validateText\" id=\"validateHubUrl\" class=\"hubField\" "
-        "type=\"text\" size=\"65\" value=\"%s\"> \n", hubUrl);
+    noCacheLabel = "Activate (Always cache)";
+    timeout = "";
+    cacheStatus = "OFF";
+    description = "Always cache contents at UCSC. This means that data access is faster, but you cannot see the effect of changes to your files on the Genome Browser for at least 5 minutes.";
     }
-else
+printf("<b style='font-size:90%%'>File caching: %s</b> &nbsp;", cacheStatus);
+printf("<button type='submit' name='udcTimeout' value='%s'>%s</button>", timeout, noCacheLabel);
+
+printf("<div class='help'>%s<br>", description);
+puts("For custom tracks, this affects only the remote formats (bigBed, bigWig, tabix, BAM, CRAM, etc), not text files (BED, PSL, etc), which are stored at UCSC.</div>");
+
+// output the measureTiming button
+char *timeLabel = "Show timings";
+char *timeVal = "on";
+char *timeDesc = "Shows loading time in milliseconds for each track, to help debug performance problems.";
+char *timeStatus = "OFF";
+if (cartNonemptyString(cart, "measureTiming")) 
     {
-    printf("<input name=\"validateText\" id=\"validateHubUrl\" class=\"hubField\" "
-        "type=\"text\" size=\"65\"> \n");
-    }
-printf("<input name=\"hubValidateButton\" id='hubValidateButton' "
-    "class=\"hubField\" type=\"button\" value=\"Check Hub settings\">\n");
-printf(" or \n");
-printf("<input name=\"hubLoadMaybeTiming\" id='hubLoadMaybeTiming' "
-    "class=\"hubField\" type=\"button\" value=\"View Hub on Genome Browser\">\n");
-printf("</div>\n"); // validateHubUrlRow div
-
-printf("<div id=\"extraSettingsContainer\" class=\"addHubBar\">\n");
-printf("<img id=\"advancedSettingsButton\" src=\"../images/add_sm.gif\">\n");
-printf("Optional View Hub Settings");
-
-char *measureTiming = cartCgiUsualString(cart, "measureTiming", NULL);
-char *udcTimeout = cartCgiUsualString(cart, "udcTimeout", NULL);
-boolean doMeasureTiming = isNotEmpty(measureTiming);
-boolean doUdcTimeout = isNotEmpty(udcTimeout);
-
-printf("<div id=\"extraSettingsList\" style=\"display: none\">\n");
-printf("<ul style=\"list-style-type:none\">\n<li>\n");
-// measureTiming first
-printf("<input name=\"addMeasureTiming\" id=\"addMeasureTiming\" "
-    "class=\"hubField\" type=\"checkbox\" %s>", doMeasureTiming ? "checked": "");
-printf("<label for=\"addMeasureTiming\">Display load times</label>\n");
-
-// and a tooltip explaining this checkbox
-printf("<div class=\"tooltip\"> (?)\n");
-printf("<span class=\"tooltiptext\">"
-    "Checking this box shows the timing measurements at the bottom of the Genome Browser page. "
-    "Useful for determining slowdowns to loading or drawing tracks."
-    "</span>\n");
-printf("</div></li>\n"); // tooltip div
-
-printf("<li>\n");
-// udcTimeout enable/disable
-printf("<input name=\"disableUdcTimeout\" id=\"disableUdcTimeout\" "
-    "class=\"hubField\" type=\"checkbox\" %s >", doUdcTimeout ? "checked" : "");
-printf("<label for=\"disableUdcTimeout\">Enable hub refresh</label>\n");
-// add a tooltip explaining these checkboxes
-printf("<div class=\"tooltip\"> (?)\n");
-printf("<span class=\"tooltiptext\">"
-    "Checking this box changes the cache expiration time (default of 5 minutes) "
-    "and allows the Genome Browser to reload Hub configuration and data files with each refresh."
-    "</span>\n");
-printf("</div></li>\n"); // tooltip div
-printf("</ul>\n");
-printf("</div>\n"); // extraSettingsList div
-printf("</div>\n"); // extraSettingsContainer div
-
-if (hubUrl != NULL && doHubValidate)
-    doValidateNewHub(hubUrl);
-else
-    printf("<div id=\"hubDeveloperInstructions\">Enter URL to hub to check configuration settings "
-        "or load hub </div> \n");
-printf("</div>"); // hubDeveloper div
-
-jsOnEventById("click", "hubValidateButton",
-    "var validateText = document.getElementById('validateHubUrl');"
-    "var udcTimeout = document.getElementById('disableUdcTimeout').checked === true;"
-    "var doMeasureTiming = document.getElementById('addMeasureTiming').checked === true;"
-    "validateText.value=$.trim(validateText.value);"
-    "if(validateUrl($('#validateHubUrl').val())) { "
-    " document.validateHubForm.elements['validateHubUrl'].value=validateText.value;"
-    " document.validateHubForm.elements['" hgHubDoHubCheck "'].value='on';"
-    " if (doMeasureTiming) { document.validateHubForm.elements['measureTiming'].value='1'; }"
-    " else { document.validateHubForm.elements['measureTiming'].value=''}"
-    " if (udcTimeout) { document.validateHubForm.elements['udcTimeout'].value='1'; }"
-    " else { document.validateHubForm.elements['udcTimeout'].value=''}"
-    " document.validateHubForm.submit(); return true; }"
-    "else { return false; }"
-    );
-jsOnEventById("click", "hubLoadMaybeTiming",
-    "var validateText = document.getElementById('validateHubUrl');"
-    "var udcTimeout = document.getElementById('disableUdcTimeout').checked === true;"
-    "var doMeasureTiming = document.getElementById('addMeasureTiming').checked === true;"
-    "validateText.value=$.trim(validateText.value);"
-    "if(validateUrl($('#validateHubUrl').val())) {"
-    "   loc = \"../cgi-bin/hgTracks?hgHub_do_firstDb=on&hgHub_do_redirect=on&hgHubConnect.remakeTrackHub=on\" + \"&hubUrl=\" + validateText.value;"
-    "   if (doMeasureTiming) { loc += \"&measureTiming=1\";} else { loc += \"&measureTiming=[]\"; }"
-    "   if (udcTimeout) { loc += \"&udcTimeout=5\"; } else { loc += \"&udcTimeout=[]\"; }"
-    "   window.location.href=loc; "
-    "} else { return false; }"
-    );
+        timeLabel = "Hide timings";
+        timeVal = "";
+        timeDesc = "Hide timing measurements.";
+        timeStatus = "OFF";
+    }
+
+printf("<b style='font-size:90%%'>Load times: %s</b> &nbsp;", timeStatus);
+printf("<button type='submit' name='measureTiming' value='%s'>%s</button>", timeVal, timeLabel);
+
+printf("<div class='help'>%s</div>", timeDesc);
+
+puts("</div>"); // margin-left
+puts("</div>"); // tabSection
+puts("</div>"); // #hubDeveloper
+
+jsOnEventById("click", "hubValidateButton", "makeIframe(event)");
 }
 
 void printSearchAndFilterBoxes(int searchEnabled, char *hubSearchTerms, char *dbFilter)
 /* Create the text boxes for search and database filtering along with the required
  * javscript */
 {
-char event[4096];
+printf("<FORM ACTION=\"%s\" NAME=\"searchHubForm\">\n",  "../cgi-bin/hgHubConnect");
 if (searchEnabled)
     {
-    safef(event, sizeof(event), 
-            "document.searchHubForm.elements['hubSearchTerms'].value=$('#hubSearchTerms').val();"
-            "document.searchHubForm.elements['hubDbFilter'].value=$('#hubDbFilter').val();"
-            "document.searchHubForm.submit();return true;");
-    printf("Enter search terms to find in public track hub description pages:<BR>"
+    cgiMakeHiddenVar(hgHubDoSearch, "on");
+    cgiMakeHiddenVar(hgHubDbFilter, "");
+    cartSaveSession(cart);
+
+    printf("The list below can be filtered on words in the description pages or assemblies.<BR>"
+            "Search terms: "
             "<input name=\"hubSearchTerms\" id=\"hubSearchTerms\" class=\"hubField\" "
-            "type=\"text\" size=\"65\" value=\"%s\"> \n",
+            "type=\"text\" size=\"50\" value=\"%s\"> \n",
             hubSearchTerms!=NULL?hubSearchTerms:"");
-    printf("<br>\n");
-    }
-else
-    {
-    safef(event, sizeof(event), 
-            "document.searchHubForm.elements['hubDbFilter'].value=$('#hubDbFilter').val();"
-            "document.searchHubForm.submit();return true;");
+    printf("\n");
     }
 
-printf("Filter hubs by assembly: "
+printf("Assembly: "
         "<input name=\"%s\" id=\"hubDbFilter\" class=\"hubField\" "
         "type=\"text\" size=\"10\" value=\"%s\"> \n"
         "<input name=\"hubSearchButton\" id='hubSearchButton' "
-        "class=\"hubField\" type=\"button\" value=\"Search Public Hubs\">\n",
+        "class=\"hubField\" type=\"submit\" value=\"Search Public Hubs\">\n",
         hgHubDbFilter, dbFilter!=NULL?dbFilter:"");
-jsOnEventById("click", "hubSearchButton", event);
-puts("<br><br>\n");
+puts("</FORM>");
 }
 
 
 void printSearchTerms(char *hubSearchTerms)
 /* Write out a reminder about the current search terms and a note about
  * how to navigate detailed search results */
 {
 printf("Displayed list <B>restricted by search terms:</B> %s\n", hubSearchTerms);
 puts("<input name=\"hubDeleteSearchButton\" id='hubDeleteSearchButton' "
         "class=\"hubField\" type=\"button\" value=\"Show All Hubs\">\n");
 
 jsOnEventById("click", "hubDeleteSearchButton",
         "document.searchHubForm.elements['hubSearchTerms'].value='';"
         "document.searchHubForm.elements['hubDbFilter'].value='';"
         "document.searchHubForm.submit();return true;");
 puts("<BR><BR>\n");
 printf("When exploring the detailed search results for a hub, you may right-click "
         "on an assembly or track line to open it in a new window.\n");
 puts("<BR><BR>\n");
 }
 
 
 void printHubListHeader()
 /* Write out the header for a list of hubs in its own table */
 {
-puts("<I>Clicking Connect redirects to the gateway page of the selected hub's default assembly.</I><BR>");
-printf("<table id=\"publicHubsTable\" class=\"hubList\"> "
+puts("<table id=\"publicHubsTable\" class=\"hubList\"> "
         "<thead><tr> "
             "<th>Display</th> "
             "<th>Hub Name</th> "
             "<th>Description</th> "
-            "<th>Assemblies</th> "
-        "</tr></thead></table>\n");
+            //"<th>Assemblies</th> "
+            "<th>Assemblies<span class='assemblyClickNote'>Click to connect and browse directly</span></th> "
+        "</tr></thead>");
 }
 
 void outputPublicTableRow(struct hubEntry *hubInfo, int count)
 /* Prints out a table row with basic information about a hub and a button
  * to connect to that hub */
 {
 int id = hubInfo->id;
 char jsId[256];
 struct slName *dbListNames = slNameListFromComma(hubInfo->dbList);
 printf("<tr>\n");
 if (id != 0)
     {
     ourCellStart();
     char hubName[32];
     safef(hubName, sizeof(hubName), "%s%u", hgHubConnectHubVarPrefix, id);
@@ -1098,36 +1105,39 @@
 /* Compare to sort based on shortLabel */
 {
 const struct hubEntry *a = *((struct hubEntry **)va);
 const struct hubEntry *b = *((struct hubEntry **)vb);
 
 return strcasecmp(a->shortLabel, b->shortLabel);
 }
 
 
 void printHubList(struct slName *hubsToPrint, struct hash *hubLookup, struct hash *searchResultHash)
 /* Print out a list of hubs, possibly along with search hits to those hubs.
  * hubLookup takes hub URLs to struct hubEntry
  * searchResultHash takes hub URLs to struct hubSearchText * (list of hits on that hub)
  */
 {
+printf("<FORM ACTION=\"%s\" id='publicHubForm' NAME=\"publicHubForm\">\n",  "../cgi-bin/hgGateway");
+cgiMakeHiddenVar(hgHubConnectRemakeTrackHub, "on");
+cartSaveSession(cart);
+
 int count = 0;
 struct hubEntry *hubList = NULL;
 struct hubEntry *hubInfo;
 long slTime;
 long printOutputForHubTime;
-boolean measureTiming = cartUsualBoolean(cart, "measureTiming", FALSE);
 if (hubsToPrint != NULL)
     {
     printHubListHeader();
 
     if (searchResultHash == NULL) // if not displaying search results, join the hub <tr>s into one table
         printf("<table class='hubList'><tbody>\n");
     struct slName *thisHubName = NULL;
     for (thisHubName = hubsToPrint; thisHubName != NULL; thisHubName = thisHubName->next)
         {
         hubInfo = (struct hubEntry *) hashFindVal(hubLookup, thisHubName->name);
         if (hubInfo == NULL)
             {
             /* This shouldn't happen often, but maybe the search hits list was built from an outdated
              * search text file that includes hubs for which no info is available.
              * Skip this hub. */
@@ -1171,112 +1181,133 @@
     }
 jsInline(
         "function lineUpCols()\n"
         "    {\n"
         "    var tableList = $('table.hubList');\n"
         "    if (tableList.length == 0)\n"
         "        return;\n"
         "    var colWidths = new Array();\n"
         "    var combinedTrackTable = $('#hideThisTable');\n"
         "    for (i=0; i<combinedTrackTable[0].rows[0].cells.length; i++)\n"
         "        colWidths[i] = combinedTrackTable[0].rows[0].cells[i].clientWidth;\n"
         "    $('#hideThisDiv')[0].style.display = 'none';\n"
         "    for(i=0; i<tableList.length; i++)\n"
         "        {\n"
         "        for(j=0; j<tableList[i].rows[0].cells.length; j++)\n"
-        "            tableList[i].rows[0].cells[j].style.width = colWidths[j]+'px';\n"
+        "            tableList[i].rows[0].cells[j].style.width = (colWidths[j])+'px';\n"
         "        }\n"
         "    }\n"
         "window.onload = lineUpCols();\n"
         );
 if (searchResultHash != NULL)
     jsInline("hubSearchTree.init(true);\n");
-}
 
+puts("</FORM>");
+}
 
 static bool outputPublicTable(struct sqlConnection *conn, char *publicTable, char *statusTable,
         struct hash **pHash)
 /* Put up the list of public hubs and other controls for the page. */
 {
 char *hubSearchTerms = cartOptionalString(cart, hgHubSearchTerms);
 char *dbFilter = cartOptionalString(cart, hgHubDbFilter);
 char *lcDbFilter = cloneString(dbFilter);
 if (isNotEmpty(lcDbFilter))
     tolowers(lcDbFilter);
 
 // make sure all the public hubs are in the hubStatus table.
+long lastTime= clock1000();
 addPublicHubsToHubStatus(cart, conn, publicTable, statusTable);
+printf("Time of addPublicHubsToHubStatus: %ld <br>", (clock1000()-lastTime));
+
 
 // build full public hub lookup hash, taking each URL to struct hubEntry * for that hub
 struct hash *hubLookup = buildPublicLookupHash(conn, publicTable, statusTable, pHash);
 
-printf("<div id=\"publicHubs\" class=\"hubList\"> \n");
+puts("<div id=\"publicHubs\" class=\"hubList\">");
+
+puts("<div class='tabSection' style='border-bottom:none'>");
+
+printf(
+    "<P>"
+    "Track data hubs are collections of external tracks that can be added to the UCSC Genome Browser. "
+    "Click \"Connect\" below to show the \"Genomes\" page with all assemblies covered by a hub. "
+    "Hub tracks then show up under the hub's own blue bar track group under the browser graphic, "
+    "as well as under \"Genome Browser > Configure\". See the "
+    "<A HREF=\"../goldenPath/help/hgTrackHubHelp.html\" TARGET=_blank style='color:#121E9A'>"
+    "User's Guide</A>. "
+    "</P>"
+    "<P>Track Hubs are created and maintained by external sources."
+    " The public hubs below were submitted to us, we have performed "
+    " basic quality checks. UCSC is not responsible for their content.</P>"
+);
 
 char *hubSearchTableName = hubSearchTextTableName();
 int searchEnabled = sqlTableExists(conn, hubSearchTableName);
 
 printSearchAndFilterBoxes(searchEnabled, hubSearchTerms, dbFilter);
 
 struct hash *searchResultHash = NULL;
 struct slName *hubsToPrint = NULL;
 if (searchEnabled && !isEmpty(hubSearchTerms))
     {
     printSearchTerms(hubSearchTerms);
     // Forcing checkDescriptions to TRUE right now, but we might want to add this as a
     // checkbox option for users in the near future.
     bool checkDescriptions = TRUE;
     searchResultHash = newHash(5);
     getHubSearchResults(conn, hubSearchTableName,
-            hubSearchTerms, checkDescriptions, lcDbFilter, hubLookup, &searchResultHash, &hubsToPrint, NULL);
-    }
+            hubSearchTerms, checkDescriptions, lcDbFilter, hubLookup, &searchResultHash, &hubsToPrint, NULL); }
 else
     {
     // There is no active search, so just add all hubs to the list
     struct hashEl *hel;
     struct hashEl *helList;
     helList = hashElListHash(hubLookup);
     for (hel = helList; hel != NULL; hel = hel->next)
         {
         if (isNotEmpty(lcDbFilter))
             {
             struct hubEntry *hubEnt = (struct hubEntry *) hel->val;
             char *lcDbList = cloneString(hubEnt->dbList);
             if (isNotEmpty(lcDbList))
                 tolowers(lcDbList);
             if ((lcDbList == NULL) || (stringIn(lcDbFilter, lcDbList) == NULL))
                 continue;
             }
         slNameAddHead(&hubsToPrint, hel->name);
         }
     }
 slReverse(&hubsToPrint);
 
+printf("</div>\n"); // .tabSection
+
 printHubList(hubsToPrint, hubLookup, searchResultHash);
-printf("</div>");
+printf("</div>"); // #publicHubs
 return (hubsToPrint != NULL);
 }
 
-
 struct hash *hgHubConnectPublic()
 /* Put up the list of public hubs and other controls for the page. */
 {
 struct hash *retHash = NULL;
 struct sqlConnection *conn = hConnectCentral();
 char *publicTable = cfgOptionEnvDefault("HGDB_HUB_PUBLIC_TABLE", 
 	hubPublicTableConfVariable, defaultHubPublicTableName);
 char *statusTable = cfgOptionEnvDefault("HGDB_HUB_STATUS_TABLE", 
 	hubStatusTableConfVariable, defaultHubStatusTableName);
+
 if (!(sqlTableExists(conn, publicTable) && 
 	outputPublicTable(conn, publicTable,statusTable, &retHash)) )
     {
     printf("<div id=\"publicHubs\" class=\"hubList\"> \n");
     printf("No Public Track Hubs found that match search criteria.<BR>");
     printf("</div>");
     }
 hDisconnectCentral(&conn);
 
 return retHash;
 }
 
 
 static void tryHubOpen(unsigned id)
 /* try to open hub, leaks trackHub structure */
@@ -1366,280 +1397,253 @@
 static void doClearHub(struct cart *theCart)
 {
 char *url = cartOptionalString(cart, hgHubDataText);
 
 printf("<pre>clearing hub %s\n",url);
 if (url != NULL)
     hubClearStatus(url);
 else
     errAbort("must specify url in %s\n", hgHubDataText);
 printf("<pre>Completed\n");
 }
 
 
 static void checkTrackDbs(struct hubConnectStatus *hubList)
 {
+//long beforeCheck = clock1000();
+
 struct hubConnectStatus *hub = hubList;
 
 for(; hub; hub = hub->next)
     {
     struct errCatch *errCatch = errCatchNew();
     if (errCatchStart(errCatch))
 	{
 	hubAddTracks(hub, database);
 	}
     errCatchEnd(errCatch);
     if (errCatch->gotError)
 	{
 	hub->errorMessage = cloneString(errCatch->message->string);
 	hubUpdateStatus( errCatch->message->string, hub);
 	}
     else
 	hubUpdateStatus(NULL, hub);
     }
+
+if (measureTiming)
+    printf("hgHubConnect: checkTrackDbs time: %lu millis<BR>\n", clock1000() - beforeCheck);
 }
 
 int hubConnectStatusCmp(const void *va, const void *vb)
 /* Compare to sort based on shortLabel */
 {
 const struct hubConnectStatus *a = *((struct hubConnectStatus **)va);
 const struct hubConnectStatus *b = *((struct hubConnectStatus **)vb);
 struct trackHub *ta = a->trackHub;
 struct trackHub *tb = b->trackHub;
 
 if ((ta == NULL) || (tb == NULL))
     return 0;
 
 return strcasecmp(tb->shortLabel, ta->shortLabel);
 }
 
+void printIncludes() 
+/* print the CSS and javascript include lines */
+{
+printf(
+"<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/themes/default/style.min.css\" />\n"
+"<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js\"></script>\n"
+"<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/jstree.min.js\"></script>\n"
+"<style>.jstree-default .jstree-anchor { height: initial; } </style>\n"
+);
+jsIncludeFile("utils.js", NULL);
+jsIncludeFile("jquery-ui.js", NULL);
+webIncludeResourceFile("jquery-ui.css");
+jsIncludeFile("ajax.js", NULL);
+jsIncludeFile("hgHubConnect.js", NULL);
+webIncludeResourceFile("hgHubConnect.css");
+jsIncludeFile("jquery.cookie.js", NULL);
+jsIncludeFile("spectrum.min.js", NULL); // there is no color picker used anywhere on this page. why include this?
+}
+
 void doMiddle(struct cart *theCart)
 /* Write header and body of html page. */
 {
 cart = theCart;
+measureTiming = cartUsualBoolean(cart, "measureTiming", FALSE);
 
 if(cgiIsOnWeb())
     checkForGeoMirrorRedirect(cart);
 
 if (cartVarExists(cart, hgHubDoClear))
     {
     doClearHub(cart);
     cartWebEnd();
     return;
     }
 
 if (cartVarExists(cart, hgHubCheckUrl))
     {
     doResetHub(cart);
     }
 
 if (cartVarExists(cart, hgHubDoRedirect))
     {
     if (doRedirect(cart))
 	{
 	cartWebEnd();
 	return;
 	}
     }
 
+if (cartVarExists(cart, hgHubDoHubCheck))
+    {
+    puts("<html>");
+    puts("<body>");
+    puts("<link rel='stylesheet' href='../style/HGStyle.css' type='text/css'>");
+    printIncludes();
+
+    jsInline("trackData = [];\n");
+    char *hubUrl = cartOptionalString(cart, "validateHubUrl");
+    jsInline("document.body.style.margin = 0;\n");
+    // simulate the look and feel of a dialog window with a blue bar at the top
+    puts("<div id='titlebar' style='background: #D9E4F8; border: 1px outset #000088; padding: 10px'>"
+        "<span id='title' style='font-weight: bold; margin: .2em 0 .1em; color: #000088'>Hub Check</span>"
+        "<a href='#' id='windowX' style='float: right' class='ui-dialog-titlebar-close ui-corner-all ui-state-hover' role='button'>"
+        "<span class='ui-icon ui-icon-closethick'>close</span></a></div>");
+
+    puts("<div id='content' style='margin:10px; padding: 2px'>");
+    if (isEmpty(hubUrl))
+        printf("Please wait, loading and checking hub, this can take 15 seconds to several minutes.");
+    else
+        {
+        puts("<p><button id='reloadButton'>Check again</button>");
+        puts("&nbsp;&nbsp;<button id='closeButton'>Close this window</button></p>");
+        jsOnEventByIdF("click", "reloadButton", "reloadIframe()");
+        jsOnEventByIdF("click", "closeButton", "closeIframe()");
+        jsOnEventByIdF("click", "windowX", "closeIframe()");
+
+        printf("<div>Finished checking %s</div>", hubUrl);
+        doValidateNewHub(hubUrl);
+        puts("<p>Our command line tool <a href='https://hgdownload.soe.ucsc.edu/downloads.html#utilities_downloads'>hubCheck</a> "
+                "can be used to obtain the same output from a Unix command line.</p>");
+        }
+    puts("</div>"); // margin 10px
+    puts("</div>"); // ui-dialog-titlebar
+    puts("</div>"); // ui-dialog
+    cartWebEnd();
+    return;
+    }
+
 cartWebStart(cart, NULL, "%s", pageTitle);
 
-printf(
-"<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/themes/default/style.min.css\" />\n"
-"<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js\"></script>\n"
-"<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/jstree.min.js\"></script>\n"
-"<style>.jstree-default .jstree-anchor { height: initial; } </style>\n"
-);
-jsIncludeFile("utils.js", NULL);
-jsIncludeFile("jquery-ui.js", NULL);
-webIncludeResourceFile("jquery-ui.css");
-jsIncludeFile("ajax.js", NULL);
-jsIncludeFile("hgHubConnect.js", NULL);
-webIncludeResourceFile("hgHubConnect.css");
-jsIncludeFile("jquery.cookie.js", NULL);
-jsIncludeFile("spectrum.min.js", NULL);
+printIncludes();
 
-boolean doHubDevMode = cfgOptionBooleanDefault("hgHubConnect.validateHub", FALSE);
-printf("<div id=\"hgHubConnectUI\"> <div id=\"description\"> \n");
-printf(
-    "<P>Track data hubs are collections of external tracks that can be imported into the UCSC Genome Browser. "
-    "Hub tracks show up under the hub's own blue label bar on the main browser page, "
-    "as well as on the configure page. For more information, including where to "
-    "<A HREF=\"../goldenPath/help/hgTrackHubHelp.html#Hosting\">host</A> your track hub, see the "
-    "<A HREF=\"../goldenPath/help/hgTrackHubHelp.html\" TARGET=_blank>"
-    "User's Guide</A>."
-    "To import a public hub click its \"Connect\" button below.</P>"
-    "<P><B>NOTE: Because Track Hubs are created and maintained by external sources,"
-    " UCSC is not responsible for their content.</B></P>"
-   );
-printf("</div>\n");
-if (doHubDevMode)
-    {
-    printf("<div id=\"developerModeDescription\" style=\"display:none\"> \n");
-    printf("<p>Check the configuration settings for a hub, including the settings "
-        "in the hub.txt, genomes.txt, and trackDb.txt files. If there are errors in the hub "
-        "setup, a heirarchal tree of tracks is presented with the errors in red text. A hub "
-        "with no errors still has the tree present but needs to be clicked to be expanded to "
-        " explore the track heirarchy.</p>\n"
-        "<p>Also present are buttons to load the hub with track timing measuring turned on "
-        "in order to profile slow loading tracks, and an option to disable the track caching "
-        "mechanism so the Genome Browser picks up changes to the configuration files or "
-        "track data files. For more information on making track hubs, please see the following links: \n "
-        "<ul>\n"
-        "<li><a href=\"../goldenPath/help/trackDb/trackDbHub.html\">Track Hub Settings</a></li> \n"
-        "<li><a href=\"../goldenPath/help/hgTrackHubHelp#Hosting\">Where to host your track hub</a></li> \n"
-        "</ul> \n"
-        "</div>\n "
-        );
+if (cartVarExists(cart, hgHubDoHubCheck))
+    {
+    jsInline("trackData = [];\n");
+    char *hubUrl = cartOptionalString(cart, "validateHubUrl");
+    doValidateNewHub(hubUrl);
+    cartWebEnd();
+    return;
     }
 
 // this variable is used by hub search and hub validate, initialize here so we don't
 // overwrite it unintentionally depending on which path the CGI takes
 jsInline("trackData = [];\n");
 
 getDbAndGenome(cart, &database, &organism, oldVars);
 
 char *survey = cfgOptionEnv("HGDB_HUB_SURVEY", "hubSurvey");
 char *surveyLabel = cfgOptionEnv("HGDB_HUB_SURVEY_LABEL", "hubSurveyLabel");
 
 if (survey && differentWord(survey, "off"))
     hPrintf("<span style='background-color:yellow;'><A HREF='%s' TARGET=_BLANK><EM><B>%s</EM></B></A></span>\n", survey, surveyLabel ? surveyLabel : "Take survey");
 hPutc('\n');
 
 // grab all the hubs that are listed in the cart
 struct hubConnectStatus *hubList =  hubConnectStatusListFromCartAll(cart);
 
 checkTrackDbs(hubList);
 
 slSort(&hubList, hubConnectStatusCmp);
 
-// here's a little form for the add new hub button
+// here's a little invisible form for the add new hub button
 printf("<FORM ACTION=\"%s\" NAME=\"addHubForm\">\n",  "../cgi-bin/hgHubConnect");
 cgiMakeHiddenVar("hubUrl", "");
 cgiMakeHiddenVar( hgHubDoFirstDb, "on");
 cgiMakeHiddenVar( hgHubDoRedirect, "on");
 cgiMakeHiddenVar(hgHubConnectRemakeTrackHub, "on");
 cartSaveSession(cart);
 puts("</FORM>");
 
-// this is the form for the connect hub button
+// this is the invisible form for the connect hub button
 printf("<FORM ACTION=\"%s\" NAME=\"connectHubForm\">\n",  "../cgi-bin/hgHubConnect");
 cgiMakeHiddenVar("hubUrl", "");
 cgiMakeHiddenVar("db", "");
 cgiMakeHiddenVar( hgHubDoRedirect, "on");
 cgiMakeHiddenVar(hgHubConnectRemakeTrackHub, "on");
 cartSaveSession(cart);
 puts("</FORM>");
 
-// this is the form for the disconnect hub button
+// this is the invisible form for the disconnect hub button - it's submitted via javascript
 printf("<FORM ACTION=\"%s\" NAME=\"disconnectHubForm\">\n",  "../cgi-bin/hgHubConnect");
 cgiMakeHiddenVar("hubId", "");
 cgiMakeHiddenVar(hgHubDoDisconnect, "on");
 cgiMakeHiddenVar(hgHubConnectRemakeTrackHub, "on");
 cartSaveSession(cart);
 puts("</FORM>");
 
-// this is the form for the reset hub button
+// this is the invisible form for the reset hub button - it's submitted via javascript
 printf("<FORM ACTION=\"%s\" NAME=\"resetHubForm\">\n",  "../cgi-bin/hgHubConnect");
 cgiMakeHiddenVar(hgHubCheckUrl, "");
 cartSaveSession(cart);
 puts("</FORM>");
 
-// this is the form for the search hub button
-printf("<FORM ACTION=\"%s\" NAME=\"searchHubForm\">\n",  "../cgi-bin/hgHubConnect");
-cgiMakeHiddenVar(hgHubSearchTerms, "");
-cgiMakeHiddenVar(hgHubDoSearch, "on");
-cgiMakeHiddenVar(hgHubDbFilter, "");
-cartSaveSession(cart);
-puts("</FORM>");
-
-// this is the form for the validate button
-if (doHubDevMode)
-    {
-    printf("<FORM ACTION=\"%s\" NAME=\"validateHubForm\">\n",  "../cgi-bin/hgHubConnect");
-    cgiMakeHiddenVar("validateHubUrl", "");
-    cgiMakeHiddenVar("measureTiming", "");
-    cgiMakeHiddenVar("udcTimeout", "");
-    // allows saving the old url in the search bar but not run hubCheck on it right away
-    // mostly for when you come back to hgHubConnect after just looking at hgTracks
-    cgiMakeHiddenVar(hgHubDoHubCheck, "off");
-    cartSaveSession(cart);
-    puts("</FORM>");
-    }
 
 // ... and now the main form
-printf("<FORM ACTION=\"%s\" METHOD=\"POST\" NAME=\"mainForm\">\n", "../cgi-bin/hgGateway");
-cartSaveSession(cart);
 
-// we have two tabs for the public and unlisted hubs
+// we have three tabs for the public and unlisted hubs and hub development
 printf("<div id=\"tabs\">"
        "<ul> <li><a class=\"defaultDesc\" href=\"#publicHubs\">Public Hubs</a></li>"
        "<li><a class=\"defaultDesc\" href=\"#unlistedHubs\">My Hubs</a></li> ");
-if (doHubDevMode) // put up the validate tab if hg.conf statement present
 printf("<li><a class=\"hubDeveloperDesc\" href=\"#hubDeveloper\">Hub Development</a></li>");
 printf("</ul> ");
 
+// The public hubs table is getting big and takes a while to download.
+// Jquery UI's tabs() command will layout the page, but because of
+// jsInlining, it will only be called at the end of the page. This can lead to the page "jumping".
+// To make the inline code run now, let's flush JS inlines.
+// I'm not sure that this makes a visible difference, but it doesn't do any harm either
+jsInlineFinish();
+jsInlineReset();
+
 struct hash *publicHash = hgHubConnectPublic();
-hgHubConnectUnlisted(hubList, publicHash);
-if (doHubDevMode)
-    hgHubConnectDeveloperMode();
-// add the event listener for toggling the right description text
-jsInline(
-    "var oldDesc = document.getElementById('description');\n"
-    "var newDesc = document.getElementById('developerModeDescription');\n"
-    "if (location.hash === \"#hubDeveloper\") {\n"
-    "   toggleTwoDivs(newDesc, oldDesc);\n"
-    "}\n"
-    "window.addEventListener('hashchange', function() {\n"
-    "   if (location.hash === \"#hubDeveloper\") {"
-    "       toggleTwoDivs(newDesc, oldDesc);\n"
-    "   } else {\n"
-    "       toggleTwoDivs(oldDesc, newDesc);\n"
-    "   }\n"
-    "});\n"
-    "var plusButton = document.getElementById('advancedSettingsButton');\n"
-    "plusButton.addEventListener('click', function() {\n"
-    "   var advancedSettingsDiv = document.getElementById('extraSettingsContainer');\n"
-    "   var extraSettingsList = document.getElementById('extraSettingsList');\n"
-    "   if (extraSettingsList.style.display != 'none') {\n"
-    "       extraSettingsList.style.display = 'none';\n"
-    "       imgTag = advancedSettingsDiv.getElementsByTagName('img')[0];\n"
-    "       imgTag.src = \"../images/add_sm.gif\";\n"
-    "   } else {\n"
-    "       extraSettingsList.style.display = 'block';\n"
-    "       imgTag = advancedSettingsDiv.getElementsByTagName('img')[0];\n"
-    "       imgTag.src = \"../images/remove_sm.gif\";\n"
-    "   }\n"
-    "});"
-    );
-printf("</div>");
 
-printf("<div class=\"tabFooter\">");
+hgHubConnectUnlisted(hubList, publicHash);
 
-printf("<span class=\"small\">"
-    "Contact <a href=\"../contacts.html\">us</A> to add a public hub."
-    "</span>\n");
-printf("</div>");
+hgHubConnectDeveloperMode();
 
-cgiMakeHiddenVar(hgHubConnectRemakeTrackHub, "on");
+printf("</div>"); // #tabs
 
-puts("</FORM>");
-printf("</div>\n");
 
 cartWebEnd();
 }
 
 char *excludeVars[] = {"Submit", "submit", "hc_one_url", hgHubDoHubCheck,
-    hgHubCheckUrl, hgHubDoClear, hgHubDoDisconnect,hgHubDoRedirect, hgHubDataText, 
+    hgHubCheckUrl, hgHubDoClear, hgHubDoRefresh, hgHubDoDisconnect,hgHubDoRedirect, hgHubDataText, 
     hgHubConnectRemakeTrackHub, NULL};
 
 int main(int argc, char *argv[])
 /* Process command line. */
 {
 long enteredMainTime = clock1000();
+
 oldVars = hashNew(10);
 cgiSpoof(&argc, argv);
 cartEmptyShell(doMiddle, hUserCookie(), excludeVars, oldVars);
 cgiExitTime("hgHubConnect", enteredMainTime);
 return 0;
 }