mirror of
https://github.com/stellarshenson/stellars-jupyterhub-ds.git
synced 2026-03-12 07:55:06 +00:00
fix: admin volume button for all users
- Find Edit buttons directly instead of looking for td.actions - Inject button before each Edit User button found - Works with JupyterHub React admin panel structure - Walks up DOM to find user container and extract username
This commit is contained in:
@@ -378,64 +378,56 @@
|
|||||||
let volumeModal = null;
|
let volumeModal = null;
|
||||||
let currentVolumeUsername = null;
|
let currentVolumeUsername = null;
|
||||||
|
|
||||||
// Inject "Manage Volumes" button into user action cells
|
// Inject "Manage Volumes" button into user action areas
|
||||||
function injectVolumeButtons() {
|
function injectVolumeButtons() {
|
||||||
// Find all user action cells (td.actions contains buttons like Start/Stop/Delete)
|
// JupyterHub React admin renders Edit buttons for each user
|
||||||
const actionCells = document.querySelectorAll('td.actions');
|
// Find all Edit buttons and inject our button before them
|
||||||
|
const editButtons = document.querySelectorAll('button[title="Edit User"], a[title="Edit User"]');
|
||||||
|
|
||||||
actionCells.forEach(cell => {
|
editButtons.forEach(editBtn => {
|
||||||
// Skip if already has our button
|
// Skip if already has our button nearby
|
||||||
if (cell.querySelector('.admin-manage-volumes-btn')) return;
|
const parent = editBtn.parentNode;
|
||||||
|
if (parent.querySelector('.admin-manage-volumes-btn')) return;
|
||||||
|
|
||||||
// Find the username from the row
|
// Find username - look for links in parent containers
|
||||||
const row = cell.closest('tr');
|
|
||||||
if (!row) return;
|
|
||||||
|
|
||||||
// Try multiple methods to find username:
|
|
||||||
// 1. Link to user page: /hub/admin#/users/{username}
|
|
||||||
// 2. Link to user server: /user/{username}
|
|
||||||
// 3. First cell text content
|
|
||||||
let username = null;
|
let username = null;
|
||||||
|
|
||||||
// Method 1: Look for admin user link (e.g., href="#/users/konrad")
|
// Walk up to find the user row/card container
|
||||||
const adminLink = row.querySelector('td a[href*="#/users/"]');
|
let container = editBtn.closest('tr') || editBtn.closest('.card') || editBtn.closest('[class*="user"]') || editBtn.parentNode.parentNode.parentNode;
|
||||||
if (adminLink) {
|
|
||||||
const href = adminLink.getAttribute('href');
|
|
||||||
const match = href.match(/#\/users\/([^\/]+)/);
|
|
||||||
if (match) username = decodeURIComponent(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method 2: Look for server link (e.g., href="/jupyterhub/user/konrad/")
|
if (container) {
|
||||||
if (!username) {
|
// Method 1: Admin user link (href contains #/users/)
|
||||||
const serverLink = row.querySelector('td a[href*="/user/"]');
|
const adminLink = container.querySelector('a[href*="#/users/"]');
|
||||||
if (serverLink) {
|
if (adminLink) {
|
||||||
const href = serverLink.getAttribute('href');
|
const href = adminLink.getAttribute('href');
|
||||||
const match = href.match(/\/user\/([^\/]+)/);
|
const match = href.match(/#\/users\/([^\/\?]+)/);
|
||||||
if (match) username = decodeURIComponent(match[1]);
|
if (match) username = decodeURIComponent(match[1]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Method 3: First td with a link - get text content
|
// Method 2: Server link (href contains /user/)
|
||||||
if (!username) {
|
if (!username) {
|
||||||
const firstLink = row.querySelector('td:first-child a');
|
const serverLink = container.querySelector('a[href*="/user/"]');
|
||||||
if (firstLink) {
|
if (serverLink) {
|
||||||
username = firstLink.textContent.trim();
|
const href = serverLink.getAttribute('href');
|
||||||
|
const match = href.match(/\/user\/([^\/\?]+)/);
|
||||||
|
if (match) username = decodeURIComponent(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method 3: Any link that looks like a username (first link text)
|
||||||
|
if (!username) {
|
||||||
|
const anyLink = container.querySelector('a');
|
||||||
|
if (anyLink && anyLink.textContent && !anyLink.textContent.includes('/')) {
|
||||||
|
username = anyLink.textContent.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!username) return;
|
if (!username) return;
|
||||||
|
|
||||||
// Find the Edit button (has fa-edit or fa-pencil icon) to match its styling
|
// Create button with same classes as Edit button
|
||||||
const editBtn = cell.querySelector('button[title*="dit"], button .fa-edit, button .fa-pencil')?.closest('button')
|
|
||||||
|| cell.querySelector('a[title*="dit"], a .fa-edit, a .fa-pencil')?.closest('a');
|
|
||||||
|
|
||||||
// Create button with same classes as Edit button, or fallback
|
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
if (editBtn) {
|
btn.className = editBtn.className;
|
||||||
btn.className = editBtn.className.replace(/\s+/g, ' ').trim();
|
|
||||||
} else {
|
|
||||||
btn.className = 'btn btn-light';
|
|
||||||
}
|
|
||||||
btn.classList.add('admin-manage-volumes-btn');
|
btn.classList.add('admin-manage-volumes-btn');
|
||||||
btn.innerHTML = '<i class="fa fa-database"></i>';
|
btn.innerHTML = '<i class="fa fa-database"></i>';
|
||||||
btn.title = 'Manage Volumes';
|
btn.title = 'Manage Volumes';
|
||||||
@@ -447,18 +439,8 @@
|
|||||||
openVolumeModal(username);
|
openVolumeModal(username);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Insert before Edit button if found, otherwise before last button (Delete)
|
// Insert before Edit button
|
||||||
if (editBtn) {
|
parent.insertBefore(btn, editBtn);
|
||||||
editBtn.parentNode.insertBefore(btn, editBtn);
|
|
||||||
} else {
|
|
||||||
// Find delete button (usually last, has fa-trash icon)
|
|
||||||
const deleteBtn = cell.querySelector('button .fa-trash, button .fa-times')?.closest('button');
|
|
||||||
if (deleteBtn) {
|
|
||||||
deleteBtn.parentNode.insertBefore(btn, deleteBtn);
|
|
||||||
} else {
|
|
||||||
cell.appendChild(btn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user