Files
stellars-jupyterhub-ds/services/jupyterhub/html_templates_enhanced/page.html
stellarshenson 5e563af39b feat: add Activity Monitor admin page with 3-state status and reset functionality
- 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
2026-01-20 17:08:23 +01:00

247 lines
9.2 KiB
HTML

{% macro modal(title, btn_label=None, btn_class="btn-primary") %}
{% set key = title.replace(' ', '-').lower() %}
{% set btn_label = btn_label or title %}
<div class="modal fade"
id="{{ key }}-dialog"
tabindex="-1"
role="dialog"
aria-labelledby="{{ key }}-label"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="{{ key }}-label">{{ title }}</h2>
<button type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">{{ caller() }}</div>
<div class="modal-footer">
<button type="button"
class="btn {{ btn_class }}"
data-bs-dismiss="modal"
data-dismiss="modal">{{ btn_label }}</button>
</div>
</div>
</div>
</div>
{% endmacro %}
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>
{%- block title -%}
JupyterHub
{%- endblock title -%}
</title>
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block stylesheet %}
<link rel="stylesheet"
href="{{ static_url('css/style.min.css') }}"
type="text/css" />
<link rel="stylesheet"
href="{{ static_url('css/custom.css') }}"
type="text/css" />
{% endblock stylesheet %}
{% block favicon %}
<link rel="icon" href="{{ static_url('favicon.ico') }}" type="image/x-icon">
{% endblock favicon %}
{% block scripts %}
<script src="{{static_url("components/bootstrap/dist/js/bootstrap.bundle.min.js") }}"
type="text/javascript"
charset="utf-8"></script>
<script src="{{static_url("components/requirejs/require.js") }}"
type="text/javascript"
charset="utf-8"></script>
<script src="{{static_url("components/jquery/dist/jquery.min.js") }}"
type="text/javascript"
charset="utf-8"></script>
<script src="{{static_url("js/darkmode.js") }}"
type="text/javascript"
charset="utf-8"></script>
{% endblock scripts %}
{# djlint js formatting doesn't handle template blocks in js #}
{# djlint: off #}
<script type="text/javascript">
require.config({
{% if version_hash %}
urlArgs: "v={{version_hash}}",
{% endif %}
baseUrl: '{{static_url("js", include_version=False)}}',
paths: {
components: '../components',
jquery: '../components/jquery/dist/jquery.min',
moment: "../components/moment/moment",
},
});
window.jhdata = {
base_url: "{{base_url}}",
prefix: "{{prefix}}",
{% if user %}
{#- Autoescaping in templates is turned on in JupyterHub, #}
{#- need `| safe` to prevent escaping #}
{#- `https://github.com/pallets/markupsafe/blob/2.1.4/src/markupsafe/_native.py#L6` #}
user: "{{ user.json_escaped_name | safe }}",
{% endif %}
{% if admin_access %}
admin_access: true,
{% else %}
admin_access: false,
{% endif %}
{% if not no_spawner_check and user and user.spawner.options_form %}
options_form: true,
{% else %}
options_form: false,
{% endif %}
xsrf_token: "{{ xsrf_token }}",
};
</script>
{# djlint: on #}
{% block meta %}
<meta name="description" content="JupyterHub">
<meta name="keywords" content="Jupyter, JupyterHub">
{% endblock meta %}
</head>
<body>
<noscript>
<div id='noscript'>
JupyterHub requires JavaScript.
<br>
Please enable it to proceed.
</div>
</noscript>
{% block nav_bar %}
<nav class="navbar navbar-expand-sm bg-body-tertiary mb-4">
<div class="container-fluid">
{% block logo %}
<span id="jupyterhub-logo" class="navbar-brand">
<a href="{{ logo_url or base_url }}">
<img src='{{ base_url }}logo'
alt='JupyterHub logo'
class='jpy-logo'
title='Home' />
</a>
</span>
{% endblock logo %}
{% if user %}
<button class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#thenavbar"
aria-controls="thenavbar"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
{% endif %}
<div class="collapse navbar-collapse" id="thenavbar">
<ul class="navbar-nav me-auto mb-0">
{% if user %}
{% block nav_bar_left_items %}
<li class="nav-item">
<a class="nav-link" href="{{ base_url }}home">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ base_url }}token">Token</a>
</li>
{% if 'admin-ui' in parsed_scopes %}
<li class="nav-item">
<a class="nav-link" href="{{ base_url }}admin">Admin</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ base_url }}authorize">Authorize Users</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ base_url }}activity">Activity</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ base_url }}notifications">Notifications</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ base_url }}settings">Settings</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ base_url }}change-password">Change Password</a>
</li>
{% if services %}
<li class="nav-item dropdown">
<a href="#"
class="nav-link dropdown-toggle"
data-bs-toggle="dropdown"
role="button"
aria-expanded="false">Services</a>
<ul class="dropdown-menu">
{% for service in services %}
{% block service scoped %}
<li>
<a class="dropdown-item" href="{{ service.href }}">{{ service.name }}</a>
</li>
{% endblock service %}
{% endfor %}
</ul>
</li>
{% endif %}
{% endblock nav_bar_left_items %}
{% endif %}
</ul>
<ul class="nav navbar-nav me-2">
{% block nav_bar_right_items %}
<li class="nav-item">
{% block theme_toggle %}
<button class="btn btn-sm"
id="dark-theme-toggle"
aria-label="Toggle dark mode"
title="Toggle dark mode">
<i aria-hidden="true" class="fa fa-circle-half-stroke"></i>
</button>
{% endblock theme_toggle %}
</li>
<li class="nav-item">
{% block login_widget %}
<span id="login_widget">
{% if user %}
<span class="me-1">{{ user.name }}</span>
<a id="logout"
role="button"
class="btn btn-sm btn-outline-contrast"
href="{{ logout_url }}"> <i aria-hidden="true" class="fa fa-sign-out"></i>Logout</a>
{% else %}
<a id="login"
role="button"
class="btn btn-sm btn-outline-contrast"
href="{{ login_url }}">Login</a>
{% endif %}
</span>
{% endblock login_widget %}
</li>
{% endblock nav_bar_right_items %}
</ul>
</div>
{% block header %}
{% endblock header %}
</div>
</nav>
{% endblock nav_bar %}
{% block announcement %}
{% if announcement %}
<div class="container text-center announcement alert alert-warning">{{ announcement | safe }}</div>
{% endif %}
{% endblock announcement %}
{% block main %}
{% endblock main %}
{% block footer %}
{% endblock footer %}
{% call modal('Error', btn_label='OK') %}
<div class="ajax-error alert-danger">The error</div>
{% endcall %}
{% block script %}
{% endblock script %}
</body>
</html>