Commit Graph

322 Commits

Author SHA1 Message Date
stellarshenson
da6cb127d8 docs: update custom branding with startup callback for surviving servers 2026-02-09 13:40:38 +01:00
stellarshenson
4bd130b11e fix: register favicon CHP routes for servers surviving hub restart
pre_spawn_hook only fires on new spawns. Servers already running when
JupyterHub restarts never trigger it, leaving their favicon CHP routes
missing. Add IOLoop.current().add_callback() one-shot startup callback
that iterates all active servers and registers their favicon routes
immediately after the event loop starts.
2026-02-09 13:39:56 +01:00
stellarshenson
0846cf26eb feat: serve hub favicon to JupyterLab via CHP proxy routes
JupyterLab sessions show their own default favicon because requests to
/user/{username}/static/favicons/favicon.ico route through CHP directly
to user containers, bypassing the hub.

Exploit CHP's trie-based longest-prefix-match: pre_spawn_hook registers
a per-user CHP route (/user/{username}/static/favicons/) pointing back
to the hub. CHP prefers this over the generic /user/{username}/ route.

FaviconRedirectHandler changed from BaseHandler to RequestHandler and
injected into Tornado's wildcard_router (not extra_handlers which
auto-prefixes /hub/). Handler 302-redirects to hub's static favicon.

- CHP target uses host:port only (no path) to avoid CHP path rewriting
- Tornado handler inserted at position 0 in wildcard_router rules
- Conditional on JUPYTERHUB_FAVICON_URI being non-empty
- CHP routes added idempotently per spawn, no cleanup needed
2026-02-06 21:20:15 +01:00
stellarshenson
2d3e1e7949 chore: bump version to 3.7.20
Reflects JUPYTERHUB_FAVICON_URI feature and branding defaults cleanup.
2026-02-06 17:43:49 +01:00
stellarshenson
38b4ebb821 fix: add missing sys import for favicon static path resolution
sys.prefix used in favicon copy path but sys was not imported at
module level - only imported later in a different block.
2026-02-06 17:37:26 +01:00
stellarshenson
6e5115e153 fix: use correct JupyterHub static files path for favicon copy
Static files are served from {sys.prefix}/share/jupyterhub/static/,
not from the Python module directory. os.path.dirname(jupyterhub.__file__)
resolves to site-packages/jupyterhub/ which has no static/ subdirectory.
2026-02-06 17:03:17 +01:00
stellarshenson
97f9070424 refactor: default branding env vars to empty for stock assets
JUPYTERHUB_LOGO_URI previously defaulted to file:///srv/jupyterhub/logo.svg
which doesn't exist in the image - the os.path.exists() check silently
fell through to stock JupyterHub logo anyway. Changed default to empty
across Dockerfile, config, compose.yml, and settings_dictionary.yml.
Both JUPYTERHUB_LOGO_URI and JUPYTERHUB_FAVICON_URI now consistently
default to empty, meaning stock JupyterHub assets are used unless
explicitly configured.
2026-02-06 16:59:46 +01:00
stellarshenson
ca9d9cf3f4 docs: document JUPYTERHUB_LOGO_URI and JUPYTERHUB_FAVICON_URI env vars
Add branding env variables to CLAUDE.md environment variables reference.
Both follow file:// (local) or URL (external) pattern.
2026-02-06 16:36:50 +01:00
stellarshenson
264a2af6e5 feat: add JUPYTERHUB_FAVICON_URI for deployment-specific favicons
Mirrors the existing JUPYTERHUB_LOGO_URI pattern:
- file:// URIs copy the file to JupyterHub static dir at startup
- http(s):// URLs passed directly to template as link href
- empty/unset falls back to default static_url('favicon.ico')

Enables visual differentiation between dev/staging/prod deployments.
2026-02-06 16:30:03 +01:00
stellarshenson
6aaae9939c feat: add immediate loading spinners for admin user operations
- show spinner immediately on "Add Users" click before POST completes,
  then transition message to "Generating credentials..." after response
- add spinner for user rename (PATCH) with "Renaming user..." message
- add error path cleanup hiding spinner on failed creation, empty
  response, or parse errors
2026-02-06 15:37:04 +01:00
stellarshenson
7e988b1ee4 chore: updated with the new way of localhost routing 2026-02-06 11:53:30 +01:00
stellarshenson
4ce1967481 updated with JUPYTERHUB_HOSTNAME to allow single-chunk routing 2026-02-06 09:23:45 +01:00
stellarshenson
232bb23f88 updated with .env copy 2026-02-06 08:54:38 +01:00
stellarshenson
1f6110e84f feat: upgraded certs generation 2026-02-06 08:52:57 +01:00
stellarshenson
fa9336bd71 chore: updated with different env name 2026-02-06 08:47:50 +01:00
stellarshenson
134034a203 docs: updated with better documentaiton 2026-02-06 08:29:39 +01:00
stellarshenson
f334d4b960 updated default halflife to 48 2026-02-02 14:53:31 +01:00
stellarshenson
3f06fb3399 docs: update journal with volume button positioning fix 2026-01-27 00:28:47 +01:00
stellarshenson
2c0bcc676c fix: reposition volume button on each MutationObserver tick
Button now moves to end of actions cell if React renders additional
buttons after it, ensuring consistent rightmost positioning
2026-01-27 00:28:23 +01:00
stellarshenson
d3913b4d69 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
2026-01-26 23:50:36 +01:00
stellarshenson
ea814b70f8 docs: update journal with README documentation changes 2026-01-26 13:38:08 +01:00
stellarshenson
920c8b82d5 docs: add volume sizes and resource tracking to README
- Add volume sizes to Activity Monitor feature description
- Add Volume sizes feature with per-volume breakdown tooltip
- Add JUPYTERHUB_ACTIVITYMON_VOLUMES_UPDATE_INTERVAL config option
2026-01-26 13:37:32 +01:00
stellarshenson
0905d5bd07 docs: add admin volume management to README
- Add feature bullet for admin volume management button
- Add note about admin capability in User Self-Service Workflow section
- Admins can manage any user's volumes via database icon in admin panel
2026-01-26 13:37:01 +01:00
stellarshenson
2a533f9140 docs: update journal with admin volume button feature
- Document final implementation using tr.user-row selector
- Username extracted from span[data-testid="user-name-div-{username}"]
- Button matches Edit User button styling (btn btn-light btn-xs)
- Positioned before Edit User button in actions cell
2026-01-26 13:35:26 +01:00
stellarshenson
32d645182d fix: admin volume button now appears for all users
- Find user rows via tr.user-row selector
- Get username from span[data-testid="user-name-div-{username}"]
- Find Edit User button by text content (no title attribute)
- Insert volume button before Edit User button
- Copies exact classes from Edit User button (btn btn-light btn-xs)
2026-01-26 13:32:16 +01:00
stellarshenson
a67af7a090 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
2026-01-26 13:31:30 +01:00
stellarshenson
a7ec100878 fix: admin volume button positioning and styling
- Find username via multiple methods (admin link, server link, text)
- Copy exact class from Edit button for consistent styling
- Insert button before Edit button (between server actions and edit)
- Works for all users, not just those with running servers
2026-01-26 13:28:54 +01:00
stellarshenson
f63de6454c feat: add admin volume management button
- Add Manage Volumes modal to admin panel with volume checkboxes
- MutationObserver injects database icon button into each user row
- Button uses btn-outline-secondary matching other admin buttons
- Calls existing DELETE /api/users/{username}/manage-volumes endpoint
- Admins can reset any user's volumes directly from admin panel
2026-01-26 13:25:39 +01:00
stellarshenson
9162de622f fix: volume sizes logging and tooltip cleanup
- Add _get_logger() helper for JupyterHub application logger integration
- Module-level logger doesn't inherit JupyterHub handlers, causing
  invisible logs for VolumeSizeRefresher and Volume Sizes operations
- Update all volume-related functions to use _get_logger()
- Add cache validation to prevent empty results overwriting valid cache
- Remove "(>8h/day)" suffix from activity tooltip when score > 100%
2026-01-26 12:57:32 +01:00
stellarshenson
4d1f4d1407 upraded latest version 2026-01-25 23:23:38 +01:00
stellarshenson
6ced633450 feat: add deletion spinner and initialize activity for new users
- Admin panel shows spinner with "Deleting user {username}..." during deletion
- Refactored loading modal to accept dynamic message text
- New users get initial activity sample recorded automatically
- New users show 0% activity bar instead of '--' in Activity Monitor
2026-01-25 23:18:30 +01:00
stellarshenson
5678af11f8 fix: add volume refresher tick logging for diagnostics
- Added tick completion log showing user count and total MB after each refresh
- Helps diagnose if VolumeSizeRefresher is running or died silently
FIX_VOLUME_REFRESHER_3.7.16
2026-01-25 21:50:25 +01:00
stellarshenson
d2a93a08f2 fix: Auth column width and Volumes tooltip
- Increased .col-auth width from 4em to 5em to prevent truncation
- Changed Volumes tooltip to generic "hover for breakdown" since
  volume names are autodiscovered
2026-01-25 12:50:06 +01:00
stellarshenson
845d458d75 feat: add column tooltips and authorization status column
- Added explanatory tooltips to all 9 column headers in Activity Monitor
- Added sortable Auth column showing NativeAuthenticator authorization status
- Green checkmark for authorized users, red X for not authorized
- Backend queries users_info table with graceful fallback
- Updated documentation with new column and API schema
3.7.16_cuda-13.0.2_jh-5.4.2
2026-01-25 12:44:11 +01:00
stellarshenson
ad1d37b16f feat: add 24h minimum data requirement for activity score
- Activity tooltip shows "Not enough data (Nh of 24h collected)" until
  sufficient samples collected (144 samples at 10-min intervals)
- Progress bar still renders to show emerging trend, tooltip clarifies
  percentage not yet reliable
- Added activitymon_sample_interval to template_vars for frontend access
- Rewrote docs/activity-tracking-methodology.md as comprehensive
  implementation specification covering data collection, scoring formulas,
  UI components, API endpoints, and design rationale
2026-01-25 12:25:38 +01:00
stellarshenson
05c99624d9 feat: add activity score normalization with TARGET_HOURS
Added JUPYTERHUB_ACTIVITYMON_TARGET_HOURS env var (default 8) to
normalize activity scores based on expected daily work hours.

- Raw score (% of sampled time active) normalized to target
- 8h/day worker with 33% raw score -> 100% normalized
- Progress bar capped at 5 segments (100%)
- Tooltip shows real % with "(>8h/day)" indicator if over 100%

Files: Dockerfile, settings_dictionary.yml, jupyterhub_config.py,
activity.html
2026-01-25 12:17:26 +01:00
stellarshenson
3343c568a7 docs: clarify configured vs effective half-life
Rewrote simulation section explaining why effective half-life differs
from configured half-life:

1. Decay is CONTINUOUS (24/7 in calendar time)
2. Work is SPARSE (only during work hours)
3. Decay during BREAKS (overnight with no new work)

Added single table showing effective half-life for work patterns
(12h, 10h, 8h, 6h, 4h, 2h) vs configured half-lives (24h, 48h, 72h).

Key insight: 72h configured = 18h effective for 8h/day worker.
2026-01-25 12:09:12 +01:00
stellarshenson
b7b3f0e87c docs: add half-life simulation tables for different work patterns
Added detailed simulation results showing how calendar half-life
translates to effective working-time decay:

- 10h/day (intensive): 72h -> 28.5 work hours at 50%
- 8h/day (typical): 72h -> 22.8 work hours at 50%
- 4h/day (part-time): 72h -> 11.5 work hours at 50%

Key finding: 72h calendar half-life consistently yields ~2.9 work
days at the 50% point, regardless of daily work hours. Activity
scores correctly reflect work fraction (8h/24h = 33.3%).
2026-01-25 11:54:54 +01:00
stellarshenson
cdf1e5eaa4 docs: add rationale for 72-hour half-life choice
Explains why 72h calendar half-life was chosen:
- Decay applies to wall-clock time, not working time
- Users work ~8h/day (1/3 of 24h period)
- 72h calendar = ~24h of working time = one full workday
- Prevents overnight breaks from penalizing scores

This calibrates decay to actual engagement patterns rather
than raw calendar time.
2026-01-25 11:51:20 +01:00
stellarshenson
a76c99d6ab feat: increase activity monitor half-life to 72 hours (3 days)
Changed JUPYTERHUB_ACTIVITYMON_HALF_LIFE default from 48h to 72h
for more stable activity scores. Activity from 3 days ago now has
50% weight, better suited for users with irregular schedules.

Updated: Dockerfile, custom_handlers.py, activity_sampler.py,
settings_dictionary.yml, README.md, docs/activity-tracking-methodology.md
2026-01-25 11:50:19 +01:00
stellarshenson
8980e552cf refactor: simplify Activity column header
Removed "(7 days)" suffix from Activity column header in activity.html.
The retention period is a configuration detail, not needed in the UI.
2026-01-25 11:48:49 +01:00
stellarshenson
ea5e8b730a feat: add volume size tooltip with per-volume breakdown
Activity Monitor now shows hover tooltip on volume sizes displaying
individual volume sizes (home, workspace, cache) instead of just total.

Backend changes:
- _fetch_volume_sizes() returns {total, volumes: {suffix: size}}
- ActivityDataHandler passes volume_breakdown to frontend

Frontend changes:
- formatVolumeSize() displays tooltip with sorted volume list
- Dotted underline indicates tooltip availability

Also: removed clean dependency from build targets in Makefile
2026-01-22 22:39:54 +01:00
stellarshenson
4d9a97eb82 feat: increase activity monitor half-life to 48 hours
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).
3.7.15_cuda-13.0.2_jh-5.4.2
2026-01-22 01:52:01 +01:00
stellarshenson
e70d4f7236 docs: migrated documentation folder 2026-01-22 01:46:37 +01:00
stellarshenson
92a7e8fa96 docs: add activity tracking methodology research
Research document covering industry approaches for activity tracking:
- Exponential Moving Average (EMA) with half-life decay
- Hubstaff's hour-based approach
- Daily target method (8h=100%)
- GitHub contribution graph methodology

Validates current EMA implementation aligns with industry standards.
2026-01-22 01:45:18 +01:00
stellarshenson
92149de4a4 docs: update journal with activity sampler service changes 2026-01-22 01:30:17 +01:00
stellarshenson
a737058903 feat: activity sampler as independent JupyterHub service
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.
2026-01-22 01:28:31 +01:00
stellarshenson
aaca516bae feat: add VolumeSizeRefresher for independent background refresh
- 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
2026-01-22 01:23:09 +01:00
stellarshenson
ae387e7734 refactor: use logging module instead of print statements 2026-01-22 01:17:04 +01:00
stellarshenson
30a1665e23 fix: add flush=True to volume sizes logging 2026-01-22 01:12:55 +01:00