Commit Graph

14 Commits

Author SHA1 Message Date
stellarshenson
00c2db6518 refactor: extract stellars_hub package with declarative config
Decompose monolithic jupyterhub_config.py (694 lines) and
custom_handlers.py (1896 lines) into pip-installable stellars_hub
package, then refactor the package into a pure logic library with
zero hardcoded data.

Package structure (stellars_hub/):
- core modules: auth, branding, events, gpu, groups, hooks, services,
  volumes, docker_utils, password_cache, volume_cache
- handlers/: 14 Tornado request handlers across 8 files
- activity/: monitoring subsystem (model, monitor, helpers, sampler,
  service)
- tests/: 65 pytest tests across 8 test files
- pyproject.toml with hatchling build backend

Declarative config architecture:
- jupyterhub_config.py expanded to 306-line self-documenting file with
  5 sections: env vars, data literals, logic calls, JupyterHub config,
  services/callbacks
- every import, env var, data literal, and c.* setting documented with
  inline comments
- all function parameters explicit (no hidden env reads in modules)
- handlers read config from self.settings['stellars_config'] via
  tornado_settings instead of os.environ.get()
- eliminated constants.py and configure.py from package

Deleted files:
- conf/bin/custom_handlers.py (1896 lines)
- conf/bin/activity_sampler.py (243 lines)
- stellars_hub/constants.py (58 lines)
- stellars_hub/configure.py (188 lines)

Other changes:
- Dockerfile uses multi-stage build (builder builds wheel + runs tests)
- 01_ensure_groups.py passes group list inline
- notebook_dir commented out (redundant with lab image default)
- docs/stellars-hub-package.md documents package architecture
2026-02-09 22:21:42 +01:00
stellarshenson
3512ad1023 fix: register favicon CHP routes in extra_routes to prevent stale deletion
check_routes() runs every ~5min and deletes any CHP route not in its
good_routes set. Favicon routes added only via add_route() were treated
as stale and removed after ~5 minutes. Now also registered in
app.proxy.extra_routes so check_routes() recognizes them as legitimate.
Applied to both pre_spawn_hook and startup callback for surviving servers.
2026-02-09 17:17:51 +01:00
stellarshenson
107764474d fix: resolve lab icon URIs at runtime in pre_spawn_hook
file:// icons were resolved to bare paths at config load time, but
JupyterLab extensions interpret bare paths as filesystem locations.
Moved URI resolution to pre_spawn_hook where app.hub.url provides the
hub origin at runtime, constructing fully qualified http:// URLs
(e.g., http://jupyterhub:8080/hub/static/lab-main-icon.svg).
2026-02-09 16:51:21 +01:00
stellarshenson
92b9b179d9 feat: add JupyterLab icon env vars, fix spawner env names
- add JUPYTERHUB_LAB_MAIN_ICON_URI and JUPYTERHUB_LAB_SPLASH_ICON_URI
  for custom JupyterLab main toolbar logo and splash screen icon
- file:// URIs copy to hub static dir and resolve to hub static URL,
  external URLs passed through as-is
- resolved URIs injected conditionally into DockerSpawner.environment
  as JUPYTERLAB_MAIN_ICON_URI and JUPYTERLAB_SPLASH_ICON_URI
- fix spawner env var names to match lab image expectations:
  JUPYTERHUB_SERVICE_* -> ENABLE_SERVICE_* (MLflow, Resources Monitor,
  TensorBoard), remove duplicate JUPYTERHUB_GPU_ENABLED
- update custom-branding.md with JupyterLab Icons section
2026-02-09 14:19:02 +01:00
stellarshenson
da6cb127d8 docs: update custom branding with startup callback for surviving servers 2026-02-09 13:40:38 +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
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
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
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
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