1c52aaa92828b6ecc18dd5800fe6650b91daf696 max Fri Sep 18 07:22:10 2026 -0700 hgSession: the new Sessions page also lists the sessions saved on our other servers #Preview2 week - bugs introduced now will need a build patch to fix Every geo mirror node keeps its own namedSessionDb, so a session saved on genome-euro is invisible on genome.ucsc.edu and people do not find it again. The table now holds the sessions from every node, with a new Server column saying where each one lives, and the session name links to the server that holds it. Two new hgSession actions, both answered in main() before the cart is built, so that a request from another node leaves no cart, userDb or sessionDb row behind: hgS_doSessionListJson returns this server's sessions for the user named by the login cookie, as JSON. This is what the other nodes call. hgS_doMirrorSessions asks every other gbNode for that list and returns what came back. It sends all of the requests before reading any of the answers, so the wait is the slowest node rather than the sum of them. The fetch goes through our own server rather than from the browser to the other nodes, which keeps cross-origin requests out of it: apache handles the CORS headers differently on every node, and the two mirrors are administered by Bielefeld and RIKEN, while CGI code reaches them with the release. The user's login cookies, and only those, are passed on (wikiLinkLoginCookieHeader), which works because the nodes share a cookie salt. The page never waits for any of this: the table is drawn from this server's own sessions and the rows from the other servers are merged in as they arrive, keeping the current sort, search and page. A node that does not answer, times out, or runs a release without the endpoint is named in a line under the toolbar instead, and costs nothing else. Sessions from another server are shown but not managed here: no Share, Edit, Overwrite or Delete, no bulk-select checkbox, and the Update now shortcut keeps ignoring them. Table rows are keyed by a new uid rather than by the session name, since the same name can now be in the table twice, once per server. geoMirrorThisNode() and geoMirrorOtherNodes() read the node list from the hgcentral gbNode table, so nothing new has to be configured: browser.node already says which node a server is. diff --git src/hg/htdocs/style/hgSession.css src/hg/htdocs/style/hgSession.css index e9ea331902e..052f7f169a8 100644 --- src/hg/htdocs/style/hgSession.css +++ src/hg/htdocs/style/hgSession.css @@ -1,96 +1,102 @@ /* hgSession.css - session-specific layout for the modern "My Sessions" page (hgSession.js). * * The shared UCSC house-style components (tokens, .gbPill, .gbBanner, .gbCard, .gbModal*, * .gbSection, .gbShareBox, .gbTable) live in gbModern.css, loaded alongside this file via * webIncludeResourceFile. This file only holds the pieces unique to the sessions page: the * save-current-view card, the session table's columns, and the Advanced panel. */ /* The whole app sits in #sessionApp, which also carries class "gbApp" for the design tokens. */ #sessionApp { margin:12px 0 24px; } #sessionApp .sessIntro { font-size:14px; color:var(--ink); margin:0 0 14px; } #sessionApp .sessIntro a { color:var(--accent); } #sessionApp .sessIntro a:hover { color:var(--accentHover); } /* Save-current-view card - the primary action. Neutral light-grey panel. */ #sessionApp .sessSaveCard { border:0; background:#f6f8fa; padding:14px 16px; display:flex; flex-direction:column; gap:12px; margin:0 0 20px; } #sessionApp .sessSaveHead { display:flex; align-items:baseline; gap:12px; flex-wrap:wrap; } /* Title and the current-view summary share one font size (title is just bold). */ #sessionApp .sessSaveTitle { font-size:14px; font-weight:700; color:var(--accent); } #sessionApp .sessSaveWhat { font-size:14px; } #sessionApp .sessSaveRow { display:flex; align-items:center; gap:12px; flex-wrap:wrap; } #sessionApp .sessSaveInput { flex:1 1 320px; min-width:220px; height:34px; box-sizing:border-box; padding:0 11px; font-size:14px; border:1px solid var(--btnLine); border-radius:0; background:#fff; font-family:inherit; } /* The optional description input spans the card on its own line below the name row. */ #sessionApp #sessSaveDesc { width:100%; flex:none; } #sessionApp .sessSaveCheck { display:flex; align-items:center; gap:7px; font-size:13px; color:var(--ink); white-space:nowrap; } /* One-click "update the most recently saved session" - a compact thin bar above the save card. */ #sessionApp .sessRecent { display:flex; align-items:center; gap:8px; flex-wrap:wrap; border:0; background:#f6f8fa; padding:6px 12px; font-size:14px; margin:0 0 12px; } #sessionApp .sessRecent .gbPill { padding:2px 10px; } #sessionApp .sessRecentTime { color:var(--muted); } /* Info bubble (navy circled i) used for the recent-update hint and session descriptions. */ #sessionApp .sessInfo { color:var(--accent); cursor:help; font-size:15px; line-height:1; } /* Section heading ("Your saved sessions") separating the save card from the table. */ #sessionApp .gbSection { margin-top:4px; } +/* Note at the end of the table toolbar, pointing at our other servers: they keep their own + * sessions, so a session saved there is not in this list. */ +#sessionApp .sessMirrorNote { margin:0; } +#sessionApp .sessMirrorNote a { color:var(--accent); } +#sessionApp .sessMirrorNote a:hover { color:var(--accentHover); } + /* Toolbar above the table: DataTables search box, page-length menu and the Select button. */ #sessionApp .sessToolbar { display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin:0 0 10px; } #sessionApp #sessionAppTable_length { margin:0; font-size:14px; color:var(--ink); } #sessionApp #sessionAppTable_length select { border:1px solid var(--btnLine); border-radius:0; padding:4px 6px; font-size:14px; background:#fff; margin:0 4px; } #sessionApp #sessionAppTable_filter { margin:0; } #sessionApp #sessionAppTable_filter input { width:280px; max-width:55vw; border:1px solid var(--btnLine); border-radius:0; padding:5px 9px; font-size:14px; } #sessionApp #sessionAppTable_wrapper { overflow-x:auto; } /* Session table columns. Left-align every header and cell (including Actions) for one clean edge. */ #sessionAppTable thead th, #sessionAppTable tbody td { text-align:left; } /* Bulk-select checkbox column (hidden until "Select" is pressed). */ #sessionAppTable td.sessSelCol, #sessionAppTable th.sessSelCol { width:26px; text-align:center; } #sessionAppTable td.sessNameCol a { font-weight:700; } /* Saved region (band, locus, Mbp position), shown inline after the assembly at normal text * size and color, separated by whitespace. */ #sessionAppTable .sessPos { white-space:nowrap; } #sessionAppTable td.sessActionsCol { white-space:nowrap; } /* All action controls (text and icon-only) share one height so they line up. */ #sessionAppTable td.sessActionsCol .gbPill { display:inline-flex; align-items:center; justify-content:center; height:24px; padding:0 9px; font-size:12px; margin-left:5px; box-sizing:border-box; vertical-align:middle; } #sessionAppTable td.sessActionsCol .gbPill.danger { padding:0 8px; } #sessionAppTable .sessIcon { width:17px; height:17px; display:block; } #sessionAppTable .sessPublic { display:inline-block; margin-left:6px; font-size:11px; font-weight:700; color:#1f7a34; } /* Lock marks a private (not-shared) session; sharing is the default so only the exception is marked. */ #sessionAppTable .sessLockWrap { margin-left:6px; cursor:help; } #sessionAppTable .sessLock { width:10px; height:12px; display:inline-block; vertical-align:-2px; color:var(--muted); } /* Advanced collapsible - the rarer load/backup/reset options. Collapsed it is a thin bar; sits * between the save card and the sessions table. */ #sessionApp .sessAdv { border:0; margin:0 0 20px; } #sessionApp .sessAdvHead { display:flex; align-items:center; gap:9px; padding:11px 14px; background:#f6f8fa; cursor:pointer; font-size:14px; font-weight:700; color:var(--accent); user-select:none; } #sessionApp .sessAdvHead .caret { width:12px; display:inline-block; } #sessionApp .sessAdvBody { padding:16px 16px; display:grid; grid-template-columns:1fr 1fr; gap:18px 28px; background:#f6f8fa; font-size:14px; } @media (max-width:820px) { #sessionApp .sessAdvBody { grid-template-columns:1fr; } } #sessionApp .sessAdvItem { display:flex; flex-direction:column; gap:7px; } #sessionApp .sessAdvItem > .lab { font-weight:700; color:var(--ink); } #sessionApp .sessAdvRow { display:flex; gap:8px; flex-wrap:wrap; align-items:center; } #sessionApp .sessAdvInput { flex:1 1 140px; min-width:110px; height:32px; box-sizing:border-box; border:1px solid var(--btnLine); border-radius:0; padding:0 9px; font-size:14px; background:#fff; font-family:inherit; } #sessionApp .sessAdvLinks { display:flex; gap:16px; flex-wrap:wrap; } #sessionApp .sessAdvLinks a { color:var(--accent); } #sessionApp .sessAdvLinks a:hover { color:var(--accentHover); } /* Small inline status line for AJAX feedback (errors in maroon). */ #sessionApp .sessMsg { font-size:13px; margin:0 0 12px; } #sessionApp .sessMsg.err { color:#b1301f; font-weight:700; } #sessionApp .sessMsg.ok { color:#1f7a34; }