Changed JUPYTERHUB_ACTIVITYMON_HALF_LIFE default from 24h to 48h
for smoother decay in activity scoring. A sample from 48 hours ago
now has 50% weight (was 50% at 24h).
Activity tracking now runs as a JupyterHub managed service that starts
on boot, independent of page views. Uses REST API to fetch user data.
Changes:
- Add activity_sampler.py standalone service script
- Configure as JupyterHub service with roles/scopes in config
- Add aiohttp dependency for async HTTP requests
- Remove lazy-start from ActivityDataHandler (now independent)
- Rename env var to JUPYTERHUB_ACTIVITYMON_SAMPLE_INTERVAL
- Update settings_dictionary.yml
Volume size refresher still lazy-starts on Activity page view.
- VolumeSizeRefresher class using Tornado PeriodicCallback
- Refreshes volume sizes every hour (JUPYTERHUB_ACTIVITYMON_VOLUMES_UPDATE_INTERVAL)
- Lazy start on first Activity page access (like ActivitySampler)
- Runs first refresh immediately, then at configured interval
The sampler was expecting an app object with db and users attributes,
but handler settings don't provide that. Changed to pass handler's
self.db and self.find_user directly to the sampler.
Also added flush=True to print statements for immediate log output.
post_init_hook is not a valid JupyterHub config option - sampler never
started. Changed to lazy initialization: ActivityDataHandler starts
the sampler when admin first visits the Activity page.
Add ActivitySampler singleton class that automatically samples activity
for ALL users (active, idle, offline) at configurable interval using
Tornado's PeriodicCallback for non-blocking execution.
- Uses JUPYTERHUB_ACTIVITYMON_ACTIVITY_UPDATE_INTERVAL (default 600s)
- Starts via post_init_hook after JupyterHub initialization
- Runs first sample immediately on startup
- Logs tick statistics after each sample cycle
- Fixes: missing parenthesis and stray code from previous edit
Threshold-based coloring (green/yellow/red) doesn't work for
multi-core systems where CPU usage can exceed 100% (e.g., 6400%
on 64-core machines). Now displays plain text values.
- Activity Monitor now shows all users with historical activity, not just active servers
- Badge changed from "X active servers" to "X users (Y active)" format
- Status column now sortable (green > amber > red priority)
- Default sort changed to status descending with secondary sort by username
- Moved inline table styles to CSS classes using em units
- Added .activity-table, .settings-table, .notifications-table classes
- Simplified Activity page subtitle
- Add sampling interval config (default 600s / 10min)
- Separate from RESOURCES_UPDATE_INTERVAL (10s for UI refresh)
- Sampling interval controls how often activity samples are recorded
- Added to Dockerfile, settings_dictionary.yml, and ActivityMonitor class
- Remove "Measured X ago" timer display (adds no value)
- Change auto-refresh interval from 30s to 10s for real-time monitoring
- Add JUPYTERHUB_ACTIVITYMON_RESOURCES_UPDATE_INTERVAL env var (default 10s)
- Resource refresh updates status, CPU, memory, timers only
- Activity sampling is separate (controlled by background process)
- Remove formatTimeAgo function and related timestamp tracking
- Add clickable column sorting for User, CPU, Memory, Time Left, Last Active
- Sorting cycles: descending -> ascending -> none (default)
- Sort icons show current direction
- Last Active now shows full words ("8 minutes ago", "4 months ago")
- Proper singular/plural handling for time units
- "Measured X ago" now updates every second (shows 5s ago, 3min ago, etc.)
- Last Active column left-aligned and shows rounded times (5min, 2h, 3d)
- Removed redundant minutes/hours from time display
- ActivityMonitor now uses /data/activity_samples.sqlite instead of
JupyterHub's main database to avoid SQLite locking conflicts
- Fixes "database is locked" errors that prevented login when both
JupyterHub and ActivityMonitor wrote simultaneously
- Added "Last Active" column to activity table showing relative time
- Redesign activity bar as thin continuous bar (80x8px) with subtle dividers
- Fix inconsistent timestamp text ("Last updated" vs "Measured")
- Fix sample cleanup to always run on record_sample(), not just on inserts
- Ensures samples older than RETENTION_DAYS are properly pruned
- Add ActivitySample SQLAlchemy model for database persistence
- Add ActivityMonitor singleton with scoring, reset, lifecycle methods
- Add JUPYTERHUB_ACTIVITYMON_INACTIVE_AFTER env var (default 60 min)
- Update defaults: SAMPLE_INTERVAL=600s, RETENTION_DAYS=7
- Fix score calculation to use measured samples only (not theoretical max)
- Add 3-state status: green (active), yellow (inactive), red (offline)
- Add recently_active field in API response
- Add Reset button with confirmation dialog
- Fix green color (explicit #28a745 instead of text-success)
- Add ThreadPoolExecutor for non-blocking Docker stats
- Remove old background sampler code (on-demand sampling now)
- Bump version to 3.7.0
- user.last_activity updates on Hub page access (causes timer reset on refresh)
- server.last_activity only updates on actual JupyterLab activity
- Matches what jupyterhub-idle-culler uses for culling decisions
- Truncate requested hours to max available instead of returning error
- Add 'truncated' flag in API response with detailed message
- UI shows warning alert (yellow) when truncated, success (green) otherwise
- Warning displays 4 seconds vs 2 seconds for normal success
- Fixed extend button re-enable after successful extension
Session Extension Feature:
- Add JUPYTERHUB_IDLE_CULLER_MAX_EXTENSION env var (default 24h)
- Add SessionInfoHandler and ExtendSessionHandler API endpoints
- Add Session Status card to home page with countdown timer
- Extension tracking in spawner state (resets on server restart)
- Color-coded warnings (yellow <1h, red <30min)
Environment Settings Harmonization:
- Add all ENV defaults to Dockerfile
- Add idle culler settings to compose.yml
- Standardize logo as JUPYTERHUB_LOGO_URI with file:// prefix
- Update NVIDIA_IMAGE to nvidia/cuda:13.0.2-base-ubuntu24.04
- Move settings_dictionary.yml to services/jupyterhub/conf/
- Replace hardcoded word list with xkcdpass library
- Add JUPYTERHUB_AUTOGENERATED_PASSWORD_WORDS (default: 4)
- Add JUPYTERHUB_AUTOGENERATED_PASSWORD_DELIMITER (default: "-")
- Fix ENABLE_SIGNUP to JUPYTERHUB_SIGNUP_ENABLED in Dockerfile
- Add config/settings_dictionary.yml with category-based structure
- Categories as top-level keys (JupyterHub Core, Docker Spawner, etc.)
- Each setting has name, description, default, optional empty_display
- Update SettingsPageHandler to load from YAML file
- Add pyyaml to Dockerfile pip dependencies
- Refactor pip install to heredoc format with echo message
Add ability to send notifications to selected servers instead of all.
- New ActiveServersHandler at GET /api/notifications/active-servers
- BroadcastNotificationHandler accepts optional recipients array
- UI with "Send to all" checkbox and server selection list
- Select All/Deselect All buttons, dynamic button text
- Backward compatible - sends to all if recipients not specified