diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 5710f3e..ee5192f 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -258,52 +258,52 @@ The startup script will automatically read this list and create missing groups. ## User Self-Service Features -The platform provides two self-service features accessible from the user control panel (`/hub/home`): +### Manage Volumes -### Reset Home Volume - -**Purpose**: Allows users to delete their home directory volume and start fresh with a clean environment. +Selective volume reset allowing users to delete chosen persistent volumes (home, workspace, cache). **Requirements**: - User's JupyterLab server must be stopped -- Volume `jupyterlab-{username}_home` must exist +- Volumes dynamically read from `DOCKER_SPAWNER_VOLUMES` config **Implementation**: -- API Endpoint: `DELETE /hub/api/users/{username}/reset-home-volume` -- Handler: `services/jupyterhub/conf/bin/custom_handlers.py::ResetHomeVolumeHandler` -- Uses Docker API to safely remove the volume -- Only affects home volume - workspace and cache volumes are preserved +- API Endpoint: `DELETE /hub/api/users/{username}/manage-volumes` +- Handler: `services/jupyterhub/conf/bin/custom_handlers.py::ManageVolumesHandler` +- Template: `services/jupyterhub/templates/home.html` with dynamic checkbox generation +- Volume list extracted via `get_user_volume_suffixes(DOCKER_SPAWNER_VOLUMES)` +- Optional descriptions from `VOLUME_DESCRIPTIONS` dict (config file) +- Validates requested volumes against `USER_VOLUME_SUFFIXES` -**Permissions**: -- Users can reset their own home volume -- Admins can reset any user's home volume -- Enforced via `@admin_or_self` decorator +**Configuration** (`config/jupyterhub_config.py`): +```python +DOCKER_SPAWNER_VOLUMES = { + "jupyterlab-{username}_home": "/home", + "jupyterlab-{username}_workspace": DOCKER_NOTEBOOK_DIR, + "jupyterlab-{username}_cache": "/home/lab/.cache", +} + +VOLUME_DESCRIPTIONS = { + 'home': 'User home directory files, configurations', + 'workspace': 'Project files, notebooks, code', + 'cache': 'Temporary files, pip cache, conda cache' +} +``` + +**Permissions**: Users can manage own volumes, admins can manage any user's volumes ### Restart Server -**Purpose**: Provides one-click Docker container restart without recreating the container. +One-click Docker container restart without recreation. Preserves volumes and configuration. -**Requirements**: -- User's JupyterLab server must be running -- Container `jupyterlab-{username}` must exist +**Requirements**: User's JupyterLab server must be running **Implementation**: - API Endpoint: `POST /hub/api/users/{username}/restart-server` - Handler: `services/jupyterhub/conf/bin/custom_handlers.py::RestartServerHandler` - Uses Docker's native `container.restart(timeout=10)` method -- Preserves container identity, volumes, and configuration - Does NOT recreate container (unlike JupyterHub's stop/spawn cycle) -**Permissions**: -- Users can restart their own server -- Admins can restart any user's server -- Enforced via `@admin_or_self` decorator - -**UI Location**: -- Custom template: `services/jupyterhub/templates/home.html` -- Reset button visible when server is stopped -- Restart button visible when server is running -- Both include confirmation modals with warnings +**Permissions**: Users can restart own server, admins can restart any user's server ## Troubleshooting diff --git a/config/jupyterhub_config.py b/config/jupyterhub_config.py index c25f8be..8fb4e12 100644 --- a/config/jupyterhub_config.py +++ b/config/jupyterhub_config.py @@ -120,6 +120,14 @@ DOCKER_SPAWNER_VOLUMES = { "jupyterhub_shared": "/mnt/shared" # shared drive across hub } +# Optional descriptions for user volumes (shown in UI) +# If a volume suffix is not listed here, no description will be shown +VOLUME_DESCRIPTIONS = { + 'home': 'User home directory files, configurations', + 'workspace': 'Project files, notebooks, code', + 'cache': 'Temporary files, pip cache, conda cache' +} + # Helper function to extract user-specific volume suffixes def get_user_volume_suffixes(volumes_dict): """Extract volume suffixes from volumes dict that follow jupyterlab-{username}_ pattern""" @@ -145,9 +153,10 @@ if c is not None: # Set volumes from constant c.DockerSpawner.volumes = DOCKER_SPAWNER_VOLUMES - # Make volume suffixes available to templates + # Make volume suffixes and descriptions available to templates c.JupyterHub.template_vars = { - 'user_volume_suffixes': USER_VOLUME_SUFFIXES + 'user_volume_suffixes': USER_VOLUME_SUFFIXES, + 'volume_descriptions': VOLUME_DESCRIPTIONS } # Built-in groups that cannot be deleted (auto-recreated if missing) diff --git a/doc/ui-template-customization.md b/doc/ui-template-customization.md index b60d62d..857a5e7 100644 --- a/doc/ui-template-customization.md +++ b/doc/ui-template-customization.md @@ -8,6 +8,10 @@ JupyterHub templates extended using Jinja2 to add custom UI features (server res - Changes require Docker rebuild with `--no-cache` flag - JupyterHub 5.4.2 uses Bootstrap 5 (not Bootstrap 4) +**Template Variables** (via `c.JupyterHub.template_vars`): +- `user_volume_suffixes`: List of volume suffixes from `DOCKER_SPAWNER_VOLUMES` +- `volume_descriptions`: Optional dict mapping suffixes to descriptions + **JavaScript Integration**: All custom JavaScript wrapped in RequireJS to ensure library loading: ```javascript diff --git a/services/jupyterhub/templates/home.html b/services/jupyterhub/templates/home.html index a42ca45..b473245 100644 --- a/services/jupyterhub/templates/home.html +++ b/services/jupyterhub/templates/home.html @@ -146,6 +146,10 @@ {{ volume_suffix }}
jupyterlab-{{ user.name }}_{{ volume_suffix }} + {% if volume_descriptions and volume_suffix in volume_descriptions %} +
+ {{ volume_descriptions[volume_suffix] }} + {% endif %} {% endfor %}