diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..1ccc80c --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,29 @@ +name: Validate Dockerfile + +on: + push: + branches: + - main + tags: + - '*_cuda-*_jh-*' + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Lint Dockerfile with hadolint + uses: hadolint/hadolint-action@v3.1.0 + with: + dockerfile: services/jupyterhub/Dockerfile.jupyterhub + ignore: DL3008,DL3013,DL3015,DL3059,DL3027,DL4006,SC2006,SC1008,DL3046,SC2153,DL3001,SC2016,SC2002 + + - name: Validation completed + run: echo "Dockerfile validation completed successfully" diff --git a/RELEASE.md b/RELEASE.md index 533f9bc..21d6436 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -5,7 +5,6 @@ **User Self-Service Capabilities** - **Restart Server**: Users can restart their running JupyterLab containers directly from the control panel without admin intervention - **Manage Volumes**: Selective reset of persistent volumes (home/workspace/cache) when server is stopped - users choose which volumes to reset via checkbox interface -- **Auto-refresh**: Page automatically refreshes after Stop/Manage/Restart actions for seamless UX ## Technical Improvements diff --git a/project.env b/project.env index d647603..9b97b8e 100644 --- a/project.env +++ b/project.env @@ -3,7 +3,7 @@ PROJECT_NAME="stellars-jupyterhub-ds" PROJECT_DESCRIPTION="Multi-user JupyterHub 4 deployment platform with data science stack, GPU auto-detection, NativeAuthenticator, and isolated per-user environments spawned via DockerSpawner" # Version -VERSION="3.0.14_cuda-12.9.1_jh-5.4.2" +VERSION="3.0.20_cuda-12.9.1_jh-5.4.2" VERSION_COMMENT="Multi-user JupyterHub with GPU auto-detection, self-service volume management (home/workspace/cache), and container restart capabilities" # Author diff --git a/services/jupyterhub/templates/home.html b/services/jupyterhub/templates/home.html index 9f8639f..38c7fae 100644 --- a/services/jupyterhub/templates/home.html +++ b/services/jupyterhub/templates/home.html @@ -7,11 +7,17 @@

JupyterHub home page

- {% if default_server.active %}Stop My Server{% endif %} + {% if default_server.active %} + + + Stop My Server + + {% endif %} + {% if not default_server.active %}Start{% endif %} My Server @@ -372,27 +378,58 @@ }); // Add page refresh after Stop Server completes - $('#stop').on('click', function() { - console.log('[Stop Server] Stop button clicked, will refresh page after stop completes'); - // JupyterHub's home.js handles the actual stop - // We just need to refresh after it completes - // Monitor for when button changes state (becomes start button) - const checkInterval = setInterval(function() { - if ($('#stop').length === 0 && $('#start').length > 0) { - console.log('[Stop Server] Server stopped, waiting before refresh...'); - clearInterval(checkInterval); - setTimeout(function() { - console.log('[Stop Server] Refreshing page now'); - location.reload(); - }, 3000); // Wait 3 seconds after stop completes before refreshing - } - }, 500); + // Watch for when JupyterHub's home.js modifies the DOM after stop + console.log('[Stop Server] Setting up DOM observer'); - // Safety timeout to clear interval after 30 seconds - setTimeout(function() { - clearInterval(checkInterval); - }, 30000); - }); + const targetNode = document.querySelector('.text-center'); + if (targetNode) { + const observer = new MutationObserver(function(mutations) { + // Check if stop button was hidden (JupyterHub uses .hide() which sets display:none) + const stopButton = document.getElementById('stop'); + const startButton = document.getElementById('start'); + + // Check visibility using offsetParent (null = hidden) + const stopVisible = stopButton && stopButton.offsetParent !== null; + const startVisible = startButton && startButton.offsetParent !== null; + + console.log('[Stop Server] Mutation detected - stop visible:', stopVisible, 'start visible:', startVisible); + + if (!stopVisible && startVisible) { + // Check if start button text was changed by JupyterHub (indicates stop completed) + const startText = startButton.textContent.trim(); + console.log('[Stop Server] Start button text:', startText); + + if (startText === 'Start My Server') { + console.log('[Stop Server] JupyterHub updated DOM after stop, updating UI and refreshing...'); + observer.disconnect(); + + // Re-inject the play icon that JupyterHub removed + const iconHtml = ' Start My Server'; + $(startButton).html(iconHtml); + + // Hide Restart Server button and show Manage Volumes button + $('#restart-server-btn').hide(); + $('#manage-volumes-btn').show(); + console.log('[Stop Server] Updated button visibility - showing Manage Volumes'); + + setTimeout(function() { + console.log('[Stop Server] Refreshing now'); + location.reload(); + }, 1000); // Give user a moment to see the corrected button state + } + } + }); + + observer.observe(targetNode, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['style', 'class'], + characterData: true + }); + + console.log('[Stop Server] Observer attached to button container'); + } // Debug: Check if buttons exist console.log('[Custom Handlers] Restart button exists:', $('#restart-server-btn').length > 0);