diff --git a/project.env b/project.env index ea8f688..cfca250 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.7.0_cuda-13.0.2_jh-5.4.2" +VERSION="3.7.1_cuda-13.0.2_jh-5.4.2" VERSION_COMMENT="Activity Monitor: admin page with 3-state status, activity scoring, reset functionality" RELEASE_TAG="RELEASE_3.2.11" RELEASE_DATE="2025-11-09" diff --git a/services/jupyterhub/html_templates_enhanced/activity.html b/services/jupyterhub/html_templates_enhanced/activity.html index fb96488..c02215d 100644 --- a/services/jupyterhub/html_templates_enhanced/activity.html +++ b/services/jupyterhub/html_templates_enhanced/activity.html @@ -71,7 +71,11 @@ require(["jquery"], function($) { // Manual refresh button $('#refresh-btn').on('click', function() { - fetchActivityData(); + var btn = $(this); + btn.prop('disabled', true).html(''); + fetchActivityData(function() { + btn.prop('disabled', false).html(' Refresh'); + }); }); // Reset button @@ -98,7 +102,7 @@ require(["jquery"], function($) { }); }); - function fetchActivityData() { + function fetchActivityData(callback) { $.ajax({ url: '{{ base_url }}api/activity', method: 'GET', @@ -107,6 +111,7 @@ require(["jquery"], function($) { }, success: function(data) { renderActivityTable(data); + if (callback) callback(); }, error: function(xhr) { $('#loading-indicator').hide(); @@ -115,6 +120,7 @@ require(["jquery"], function($) { .removeClass('alert-info') .addClass('alert-danger') .html(' Failed to load activity data'); + if (callback) callback(); } }); } @@ -137,7 +143,7 @@ require(["jquery"], function($) { // Update header info $('#active-count').text(users.length + ' active server' + (users.length !== 1 ? 's' : '')); - $('#last-updated').text('Measured: ' + formatTimestamp(timestamp)); + $('#last-updated').text('Measured ' + formatTimeAgo(timestamp)); // Show sampling status if available if (samplingStatus) { @@ -255,9 +261,24 @@ require(["jquery"], function($) { return html; } - function formatTimestamp(isoString) { + function formatTimeAgo(isoString) { var date = new Date(isoString); - return date.toLocaleTimeString(); + var now = new Date(); + var diffMs = now - date; + var diffSec = Math.floor(diffMs / 1000); + var diffMin = Math.floor(diffSec / 60); + var diffHour = Math.floor(diffMin / 60); + + if (diffMin < 1) { + return 'just now'; + } else if (diffMin < 60) { + return diffMin + 'min ago'; + } else if (diffHour < 24) { + return diffHour + 'h ago'; + } else { + var diffDay = Math.floor(diffHour / 24); + return diffDay + 'd ago'; + } } function getCookie(name) {