From d3913b4d6944bb89211fe390cdc8161a71e9237c Mon Sep 17 00:00:00 2001 From: stellarshenson Date: Mon, 26 Jan 2026 23:50:36 +0100 Subject: [PATCH] fix: admin volume button only for stopped servers, positioned last - Volume management button now only appears for users with stopped servers - Button dynamically removed when server starts running - Button positioned as last element in action cell (after Edit User) - MutationObserver handles React re-renders --- .claude/JOURNAL.md | 4 ++-- .../html_templates_enhanced/admin.html | 23 +++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.claude/JOURNAL.md b/.claude/JOURNAL.md index d769d58..61e4c1f 100644 --- a/.claude/JOURNAL.md +++ b/.claude/JOURNAL.md @@ -277,8 +277,8 @@ This journal tracks substantive work on documents, diagrams, and documentation c 91. **Task - Fix Volume Sizes logging and tooltip cleanup**: Fixed invisible VolumeSizeRefresher logs and simplified activity tooltip
**Result**: **Logging Fix**: Module-level `log = logging.getLogger('jupyterhub.custom_handlers')` doesn't inherit JupyterHub's handlers, causing all VolumeSizeRefresher and Volume Sizes logs to be invisible. Added `_get_logger()` helper function that returns `Application.instance().log` for proper integration. Updated `_fetch_volume_sizes()`, `_refresh_volume_sizes_sync()`, `get_volume_sizes_with_refresh()`, and all `VolumeSizeRefresher` methods to use `_get_logger()`. Added cache validation - empty results no longer overwrite valid cached data (logs warning instead). **Tooltip Cleanup**: Removed "(>8h/day)" suffix from activity tooltip when score exceeds 100% - now shows just "Activity: X%" -92. **Task - Admin volume management button**: Added Manage Volumes button to admin panel for all users
- **Result**: Added volume management modal to admin.html with checkboxes for each volume type (home, workspace, cache), warning alert, and confirm button. JavaScript uses MutationObserver to inject database icon button into each user's action cell as React renders them. Finds all `tr.user-row` elements, extracts username from `span[data-testid="user-name-div-{username}"]`, locates Edit User button by text content, copies its exact classes (`btn btn-light btn-xs`), and inserts volume button immediately before Edit User button. Modal shows target username, selection count warning, and spinner during reset. Calls existing `DELETE /api/users/{username}/manage-volumes` endpoint with CSRF token. Admins can now reset any user's volumes directly from the admin panel +92. **Task - Admin volume management button**: Added Manage Volumes button to admin panel for users with stopped servers
+ **Result**: Added volume management modal to admin.html with checkboxes for each volume type (home, workspace, cache), warning alert, and confirm button. JavaScript uses MutationObserver to inject database icon button into each user's action cell as React renders them. Finds all `tr.user-row` elements, checks for "Stop Server" button to detect running servers (skips those), extracts username from `span[data-testid="user-name-div-{username}"]`, copies Edit User button classes (`btn btn-light btn-xs`), and appends volume button as last element in actions cell. Button is removed dynamically when server starts (MutationObserver detects DOM changes). Modal shows target username, selection count warning, and spinner during reset. Calls existing `DELETE /api/users/{username}/manage-volumes` endpoint with CSRF token. Admins can reset volumes only for users with stopped servers 93. **Task - README documentation update**: Updated README with admin volume management and volume sizes tracking
**Result**: Added Admin Volume Management feature to Features list (database icon button in admin panel). Updated Activity Monitor feature description to include volume sizes with per-volume breakdown. Added Volume sizes feature to Activity Monitor Features section with hover tooltip details. Added `JUPYTERHUB_ACTIVITYMON_VOLUMES_UPDATE_INTERVAL` config option (default 1 hour). Added note in User Self-Service Workflow section explaining admin can manage volumes for any user from `/hub/admin` diff --git a/services/jupyterhub/html_templates_enhanced/admin.html b/services/jupyterhub/html_templates_enhanced/admin.html index 5dc22b4..d361a26 100644 --- a/services/jupyterhub/html_templates_enhanced/admin.html +++ b/services/jupyterhub/html_templates_enhanced/admin.html @@ -388,8 +388,23 @@ const actionsCell = row.querySelector('td.actions'); if (!actionsCell) return; + // Check if server is running (has "Stop Server" button) or stopped (has "Start Server" button) + let serverRunning = false; + actionsCell.querySelectorAll('button').forEach(btn => { + if (btn.textContent.trim() === 'Stop Server') { + serverRunning = true; + } + }); + + // Remove existing button if server is now running + const existingBtn = actionsCell.querySelector('.admin-manage-volumes-btn'); + if (serverRunning) { + if (existingBtn) existingBtn.remove(); + return; + } + // Skip if already has our button - if (actionsCell.querySelector('.admin-manage-volumes-btn')) return; + if (existingBtn) return; // Get username from the user-name-div span const usernameSpan = row.querySelector('span[data-testid^="user-name-div-"]'); @@ -397,7 +412,7 @@ const username = usernameSpan.textContent.trim(); if (!username) return; - // Find the Edit User button (by text content) + // Find the Edit User button (by text content) to copy its styling let editBtn = null; actionsCell.querySelectorAll('button').forEach(btn => { if (btn.textContent.trim() === 'Edit User') { @@ -421,8 +436,8 @@ openVolumeModal(username); }); - // Insert before Edit button - editBtn.parentNode.insertBefore(btn, editBtn); + // Append as last button in actions cell + actionsCell.appendChild(btn); }); }