A FastHTML component library for monitoring plugin job execution with progress tracking, log tailing, resource usage, and cancellation via a tabbed modal with content overlay.
Project description
cjm-fasthtml-job-monitor
Install
pip install cjm_fasthtml_job_monitor
Project Structure
nbs/
├── components/ (6)
│ ├── tabs/ (3)
│ │ ├── logs_tab.ipynb # Log tailing view with auto-scroll.
│ │ ├── progress_tab.ipynb # Progress bar, stage message, elapsed time, and job status badge.
│ │ └── resources_tab.ipynb # Worker CPU, RAM, and GPU resource usage display.
│ ├── modal.ipynb # Tabbed modal with progress, logs, and resources tabs.
│ ├── overlay.ipynb # Semi-transparent content overlay with loading spinner.
│ └── trigger.ipynb # Trigger button and progress button for job monitor.
├── routes/ (1)
│ └── init.ipynb # Route factory for job trigger, SSE progress streaming, and cancellation.
├── services/ (1)
│ └── monitor.ipynb # Service for job execution monitoring with resource telemetry.
├── html_ids.ipynb # Prefix-based HTML ID generator for job monitor DOM elements.
└── models.ipynb # Data types for job monitor URLs, configuration, and resource snapshots.
Total: 10 notebooks across 3 directories
Module Dependencies
graph LR
components_modal[components.modal<br/>Modal Component]
components_overlay[components.overlay<br/>Overlay Components]
components_tabs_logs_tab[components.tabs.logs_tab<br/>Logs Tab]
components_tabs_progress_tab[components.tabs.progress_tab<br/>Progress Tab]
components_tabs_resources_tab[components.tabs.resources_tab<br/>Resources Tab]
components_trigger[components.trigger<br/>Trigger Components]
html_ids[html_ids<br/>HTML IDs]
models[models<br/>Models]
routes_init[routes.init<br/>Route Factory]
services_monitor[services.monitor<br/>Monitor Service]
components_modal --> models
components_modal --> html_ids
components_modal --> components_tabs_logs_tab
components_modal --> components_tabs_progress_tab
components_modal --> components_tabs_resources_tab
components_overlay --> html_ids
components_overlay --> models
components_tabs_logs_tab --> html_ids
components_tabs_progress_tab --> html_ids
components_tabs_resources_tab --> models
components_trigger --> html_ids
components_trigger --> models
routes_init --> components_overlay
routes_init --> models
routes_init --> services_monitor
routes_init --> components_modal
routes_init --> html_ids
routes_init --> components_trigger
services_monitor --> models
19 cross-module dependencies detected
CLI Reference
No CLI commands found in this project.
Module Overview
Detailed documentation for each module in the project:
HTML IDs (html_ids.ipynb)
Prefix-based HTML ID generator for job monitor DOM elements.
Import
from cjm_fasthtml_job_monitor.html_ids import (
JobMonitorHtmlIds
)
Classes
@dataclass
class JobMonitorHtmlIds:
"Prefix-based HTML ID generator for job monitor DOM elements."
prefix: str # ID prefix for this job monitor instance
def modal(self) -> str: # The modal dialog element
"""Modal dialog element."""
return f"{self.prefix}-modal"
@property
def modal_content(self) -> str: # Static modal body (not replaced by SSE)
"Modal dialog element."
def modal_content(self) -> str: # Static modal body (not replaced by SSE)
"""Modal body container (holds tabs + footer, rendered once)."""
return f"{self.prefix}-modal-content"
@property
def sse_connection(self) -> str: # Hidden div that carries SSE connection
"Modal body container (holds tabs + footer, rendered once)."
def sse_connection(self) -> str: # Hidden div that carries SSE connection
"""Hidden SSE connection element (sse-connect, receives push updates)."""
return f"{self.prefix}-sse"
# --- Overlay ---
@property
def overlay(self) -> str: # Semi-transparent content blocker
"Hidden SSE connection element (sse-connect, receives push updates)."
def overlay(self) -> str: # Semi-transparent content blocker
"""Content overlay element."""
return f"{self.prefix}-overlay"
# --- Trigger slot ---
@property
def trigger_slot(self) -> str: # Slot for trigger/progress button
"Content overlay element."
def trigger_slot(self) -> str: # Slot for trigger/progress button
"""Trigger button slot (swaps between trigger and progress button)."""
return f"{self.prefix}-trigger-slot"
# --- Progress ---
@property
def progress_bar(self) -> str: # Progress bar element
"Trigger button slot (swaps between trigger and progress button)."
def progress_bar(self) -> str: # Progress bar element
"""Progress bar element."""
return f"{self.prefix}-progress-bar"
@property
def elapsed(self) -> str: # Elapsed time display
"Progress bar element."
def elapsed(self) -> str: # Elapsed time display
"""Elapsed time display element."""
return f"{self.prefix}-elapsed"
# --- Tabs ---
@property
def tabs(self) -> str: # Tab container
"Elapsed time display element."
def tabs(self) -> str: # Tab container
"""Tab navigation container."""
return f"{self.prefix}-tabs"
@property
def tab_progress(self) -> str: # Progress tab inner content
"Tab navigation container."
def tab_progress(self) -> str: # Progress tab inner content
"""Progress tab inner content (OOB target)."""
return f"{self.prefix}-tab-progress"
@property
def tab_logs(self) -> str: # Logs tab inner content
"Progress tab inner content (OOB target)."
def tab_logs(self) -> str: # Logs tab inner content
"""Logs tab inner content (OOB target)."""
return f"{self.prefix}-tab-logs"
@property
def tab_resources(self) -> str: # Resources tab inner content
"Logs tab inner content (OOB target)."
def tab_resources(self) -> str: # Resources tab inner content
"""Resources tab inner content (OOB target)."""
return f"{self.prefix}-tab-resources"
# --- Footer ---
@property
def modal_footer(self) -> str: # Modal footer (cancel button area)
"Resources tab inner content (OOB target)."
def modal_footer(self) -> str: # Modal footer (cancel button area)
"""Modal footer (OOB target for cancel button show/hide)."""
return f"{self.prefix}-modal-footer"
# --- Logs ---
@property
def log_container(self) -> str: # Scrollable log display
"Modal footer (OOB target for cancel button show/hide)."
def log_container(self) -> str: # Scrollable log display
"Log tailing container."
Route Factory (init.ipynb)
Route factory for job trigger, SSE progress streaming, and cancellation.
Import
from cjm_fasthtml_job_monitor.routes.init import (
init_job_monitor_routes,
check_inflight_job
)
Functions
def _get_job_data(service, job_id):
"""Extract job status fields from a Job object."""
job = service.get_job(job_id)
if not job
"Extract job status fields from a Job object."
def init_job_monitor_routes(
monitor_service: JobMonitorService, # Service instance
plugin_name: str, # Target plugin for jobs
state_store: SQLiteWorkflowStateStore, # For persisting job_id
workflow_id: str, # Workflow ID for state access
step_id: str, # Step ID for state access
state_key: str, # Key in step state for job_id
prefix: str, # URL prefix
overlay_target_id: str, # ID of element to overlay
kb_system_id: Optional[str] = None, # Keyboard system ID to pause/resume
on_complete: Optional[Callable] = None, # async fn(job, request, sess) -> List[FT]
on_cancel: Optional[Callable] = None, # async fn(job, request, sess) -> List[FT]
on_fail: Optional[Callable] = None, # async fn(job, request, sess) -> List[FT]
job_args_builder: Optional[Callable] = None, # fn(state_store, workflow_id, session_id) -> (args, kwargs)
config: Optional[JobMonitorConfig] = None, # UI config
id_prefix: str = "jm", # HTML ID prefix
icon_fn: Optional[Callable] = None, # Icon renderer fn(name, **kwargs) -> FT
) -> Tuple[APIRouter, JobMonitorUrls, JobMonitorHtmlIds]: # (router, urls, ids)
"Initialize job monitor routes with SSE-based progress streaming."
def check_inflight_job(
monitor_service: JobMonitorService, # Service instance
plugin_name: str, # Target plugin
state_store: SQLiteWorkflowStateStore, # State store
workflow_id: str, # Workflow ID
session_id: str, # Session ID
step_id: str, # Step ID
state_key: str, # State key for job_id
config: JobMonitorConfig, # Display config
ids: JobMonitorHtmlIds, # Element IDs
urls: JobMonitorUrls, # Route URLs
icon_fn: Optional[Callable] = None, # Icon renderer
) -> Tuple[Optional[FT], Optional[FT], Optional[FT], bool]
"""
Check for in-flight job and return appropriate UI state.
Returns:
- Button element (trigger or progress button)
- Overlay element (active overlay or empty placeholder)
- Modal element (with SSE connection if running, or empty placeholder)
- Whether a job is currently running
"""
Variables
_TERMINAL_STATUSES
Logs Tab (logs_tab.ipynb)
Log tailing view with auto-scroll.
Import
from cjm_fasthtml_job_monitor.components.tabs.logs_tab import (
render_logs_tab
)
Functions
def render_logs_tab(
ids: JobMonitorHtmlIds, # Element IDs
logs: str = '', # Log text content
) -> FT: # Logs tab content
"Render logs tab with auto-scroll to bottom."
Modal Component (modal.ipynb)
Tabbed modal with progress, logs, and resources tabs.
Import
from cjm_fasthtml_job_monitor.components.modal import (
render_sse_connection,
render_tab_content_oob,
render_footer_oob,
render_sse_response,
get_sse_headers,
render_job_modal
)
Functions
def render_sse_connection(
ids: JobMonitorHtmlIds, # Element IDs
urls: JobMonitorUrls, # Route URLs (progress URL used for SSE endpoint)
job_id: str, # Active job ID (passed as query param)
is_active: bool = True, # Whether SSE should be connected
) -> FT: # Hidden SSE connection div
"""
Render the hidden SSE connection element.
When `is_active`, includes hx-ext=sse and sse-connect for real-time updates.
When not active, renders as an inert hidden div (no connection).
"""
def render_tab_content_oob(
ids: JobMonitorHtmlIds, # Element IDs
status: str = 'pending', # Job status
progress_value: float = 0.0, # 0.0 to 1.0
status_message: str = '', # Stage message
started_at: Optional[float] = None, # Unix timestamp
completed_at: Optional[float] = None, # Unix timestamp
logs: str = '', # Log text
resources: Optional[ResourceSnapshot] = None, # Resource data
) -> tuple: # (progress_div, logs_div, resources_div) with OOB attrs
"Render the three tab inner content divs as OOB swap targets."
def render_footer_oob(
ids: JobMonitorHtmlIds, # Element IDs
urls: JobMonitorUrls, # Route URLs
is_active: bool = True, # Whether job is active (show cancel)
) -> FT: # Footer div with OOB attr
"Render the modal footer with cancel button as OOB swap."
def _make_tab_oob(ids, tab_id, render_fn, *args, **kwargs):
"""Render a single tab's inner content as OOB swap target."""
div = Div(render_fn(*args, **kwargs), id=tab_id)
div.attrs['hx-swap-oob'] = "true"
return div
def render_sse_response(
ids: JobMonitorHtmlIds, # Element IDs
urls: JobMonitorUrls, # Route URLs
status: str = 'pending', # Job status
progress_value: float = 0.0, # 0.0 to 1.0
status_message: str = '', # Stage message
started_at: Optional[float] = None, # Unix timestamp
completed_at: Optional[float] = None, # Unix timestamp
logs: Optional[str] = None, # Log text (None = skip logs tab update)
resources: Optional[ResourceSnapshot] = None, # Resource data (None = skip resources tab update)
include_footer: bool = False, # Include footer OOB (only needed on state transitions)
extra_oob: Optional[List[FT]] = None, # Additional OOB elements (cleanup, callbacks)
) -> FT: # Div wrapping all OOB elements for sse_message()
"Render a single tab's inner content as OOB swap target."
def render_sse_response(
ids: JobMonitorHtmlIds, # Element IDs
urls: JobMonitorUrls, # Route URLs
status: str = 'pending', # Job status
progress_value: float = 0.0, # 0.0 to 1.0
status_message: str = '', # Stage message
started_at: Optional[float] = None, # Unix timestamp
completed_at: Optional[float] = None, # Unix timestamp
logs: Optional[str] = None, # Log text (None = skip logs tab update)
resources: Optional[ResourceSnapshot] = None, # Resource data (None = skip resources tab update)
include_footer: bool = False, # Include footer OOB (only needed on state transitions)
extra_oob: Optional[List[FT]] = None, # Additional OOB elements (cleanup, callbacks)
) -> FT: # Div wrapping all OOB elements for sse_message()
"""
Build the OOB update payload for an SSE push.
Selectively includes only changed elements:
- Progress tab: always included (status/progress/elapsed change frequently)
- Logs tab: included only when `logs` is not None
- Resources tab: included only when `resources` is not None
- Footer: included only when `include_footer` is True (state transitions)
"""
def get_sse_headers() -> List[FT]
"""
Return the HTMX SSE extension script + cleanup script for app headers.
Consumers should add these to their FastHTML app's hdrs list.
The SSE extension must load after the main HTMX script.
"""
def render_job_modal(
config: JobMonitorConfig, # Display config
ids: JobMonitorHtmlIds, # Element IDs
urls: JobMonitorUrls, # Route URLs
job_id: str = '', # Active job ID for SSE connection
status: str = 'pending', # Job status
progress_value: float = 0.0, # 0.0 to 1.0
status_message: str = '', # Stage message
started_at: Optional[float] = None, # Unix timestamp
completed_at: Optional[float] = None, # Unix timestamp
logs: str = '', # Log text
resources: Optional[ResourceSnapshot] = None, # Resource data
open_on_render: bool = False, # Auto-open via JS
) -> FT: # Dialog element
"""
Render the full tabbed modal dialog.
The tab structure (radio inputs + tab-content wrappers) is static.
Each tab-content wrapper contains an inner div with a stable ID
that gets OOB-swapped by the SSE stream. This prevents the
selected tab from resetting on each update.
Closable via: Escape key, X button (top-right), or clicking backdrop.
"""
Variables
_TERMINAL_STATUSES
Models (models.ipynb)
Data types for job monitor URLs, configuration, and resource snapshots.
Import
from cjm_fasthtml_job_monitor.models import (
JobMonitorUrls,
ResourceSnapshot,
JobMonitorConfig
)
Classes
@dataclass
class JobMonitorUrls:
"URL endpoints for the job monitor routes."
trigger: str # POST -- submit job
progress: str # GET -- poll progress
cancel: str # POST -- cancel job
@dataclass
class ResourceSnapshot:
"Point-in-time resource usage for a worker."
worker_pid: int # Worker process ID
cpu_percent: float # CPU utilization %
memory_rss_mb: float # Process tree RSS in MB
gpu_memory_mb: Optional[float] # Per-process GPU memory in MB
gpu_index: Optional[int] # Which GPU device
gpu_name: Optional[str] # GPU device name
gpu_total_mb: Optional[float] # Total GPU memory in MB
gpu_load_percent: Optional[float] # GPU compute utilization %
@dataclass
class JobMonitorConfig:
"Configuration for a job monitor instance."
modal_title: str = 'Job Execution' # Modal header title
trigger_label: str = 'Run' # Trigger button label
trigger_icon: Optional[str] # Lucide icon name for trigger button
progress_label: str = 'View Progress' # Progress button label (when modal closed)
sse_interval_s: float = 0.5 # Server-side SSE push interval in seconds
log_lines: int = 50 # Number of log lines to show
overlay_z_index: int = 10 # Overlay z-index
Monitor Service (monitor.ipynb)
Service for job execution monitoring with resource telemetry.
Import
from cjm_fasthtml_job_monitor.services.monitor import (
JobMonitorService
)
Functions
async def _submit_job(
self,
plugin_name: str, # Target plugin
*args,
priority: int = 0,
**kwargs
) -> str: # job_id
"Submit a job to the queue."
def _get_job(self, job_id: str) -> Optional[Job]: # Job or None
"""Get job by ID."""
return self._queue.get_job(job_id)
async def _cancel_job(self, job_id: str) -> bool: # True if cancelled
"Get job by ID."
async def _cancel_job(self, job_id: str) -> bool: # True if cancelled
"Cancel a job."
def _get_logs(
self,
plugin_name: str, # Plugin whose logs to read
lines: int = 50, # Max lines to return
current_session_only: bool = True, # Filter to current session
) -> str: # Log text
"Get plugin logs, optionally filtered to current session."
def _filter_current_session(
raw: str, # Full log text
max_lines: int, # Max lines to return
) -> str: # Filtered log text
"Extract logs from the most recent session (after last '--- Starting' marker)."
def _get_resource_snapshot(
self,
plugin_name: str, # Plugin whose worker to query
) -> Optional[ResourceSnapshot]: # Snapshot or None
"Get current resource usage for a plugin's worker."
def _enrich_gpu_stats(
self,
snapshot: ResourceSnapshot, # Snapshot to enrich in place
) -> None
"Add per-process GPU stats from system monitor plugin."
Classes
class JobMonitorService:
def __init__(
self,
queue: JobQueue, # Job queue instance
manager: PluginManager, # For worker stats, logs, sysmon
sysmon_plugin_name: Optional[str] = None, # System monitor plugin name (e.g., 'cjm-system-monitor-nvidia')
)
"Service for job execution monitoring with resource telemetry."
def __init__(
self,
queue: JobQueue, # Job queue instance
manager: PluginManager, # For worker stats, logs, sysmon
sysmon_plugin_name: Optional[str] = None, # System monitor plugin name (e.g., 'cjm-system-monitor-nvidia')
)
Overlay Components (overlay.ipynb)
Semi-transparent content overlay with loading spinner.
Import
from cjm_fasthtml_job_monitor.components.overlay import (
render_job_overlay,
render_job_overlay_placeholder
)
Functions
def render_job_overlay(
ids: JobMonitorHtmlIds, # Element IDs
config: JobMonitorConfig, # Display config (for z-index)
) -> FT: # Overlay div with centered spinner
"Render semi-transparent overlay with loading spinner."
def render_job_overlay_placeholder(
ids: JobMonitorHtmlIds, # Element IDs
) -> FT: # Empty div with overlay ID
"Render empty placeholder (swapping this in removes the overlay)."
Progress Tab (progress_tab.ipynb)
Progress bar, stage message, elapsed time, and job status badge.
Import
from cjm_fasthtml_job_monitor.components.tabs.progress_tab import (
render_progress_tab
)
Functions
def _render_status_badge(
status: str, # Job status string
) -> FT: # Badge element
"Render a colored badge for job status."
def _format_elapsed(
started_at: Optional[float], # Unix timestamp when job started
completed_at: Optional[float], # Unix timestamp when job completed
) -> str: # Formatted elapsed time string
"Format elapsed time as M:SS."
def _elapsed_timer_script(
ids: JobMonitorHtmlIds, # Element IDs
started_at: Optional[float], # Unix timestamp
) -> FT: # Script element for client-side timer
"Generate JS for client-side elapsed time updates."
def render_progress_tab(
ids: JobMonitorHtmlIds, # Element IDs
status: str = 'pending', # Job status
progress_value: float = 0.0, # 0.0 to 1.0
status_message: str = '', # Stage message
started_at: Optional[float] = None, # Unix timestamp
completed_at: Optional[float] = None, # Unix timestamp
) -> FT: # Progress tab content
"Render progress tab content."
Variables
_STATUS_BADGE_COLORS = {5 items}
Resources Tab (resources_tab.ipynb)
Worker CPU, RAM, and GPU resource usage display.
Import
from cjm_fasthtml_job_monitor.components.tabs.resources_tab import (
render_resources_tab
)
Functions
def _render_stat_row(
label: str, # Stat label (e.g., 'CPU')
value_text: str, # Formatted value (e.g., '45.2%')
bar_pct: Optional[int] = None, # Progress bar percentage (0-100)
bar_color: str = '', # DaisyUI progress color class
) -> FT: # Stat row element
"Render a single stat row with label, value, and optional progress bar."
def render_resources_tab(
resources: Optional[ResourceSnapshot] = None, # Resource data
) -> FT: # Resources tab content
"Render resources tab content."
Trigger Components (trigger.ipynb)
Trigger button and progress button for job monitor.
Import
from cjm_fasthtml_job_monitor.components.trigger import (
render_job_trigger,
render_job_progress_button
)
Functions
def render_job_trigger(
config: JobMonitorConfig, # Display config
ids: JobMonitorHtmlIds, # Element IDs
urls: JobMonitorUrls, # Route URLs
disabled: bool = False, # Disable button
icon_fn: Optional[callable] = None, # Icon renderer fn(name, **kwargs) -> FT
) -> FT: # Trigger button wrapped in slot div
"Render the initial trigger button."
def render_job_progress_button(
config: JobMonitorConfig, # Display config
ids: JobMonitorHtmlIds, # Element IDs
) -> FT: # Progress button wrapped in slot div
"Render 'View Progress' button with spinner."
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cjm_fasthtml_job_monitor-0.0.2.tar.gz.
File metadata
- Download URL: cjm_fasthtml_job_monitor-0.0.2.tar.gz
- Upload date:
- Size: 31.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30478ee6f552d705614618da0bac560c8c4772d6216998a5fcd9625eb725286c
|
|
| MD5 |
ba01573d30c34bb5e756dc0f80215866
|
|
| BLAKE2b-256 |
9605034e241fe898c05f51eed4e429dc2767473537dcf1fed347f51051313eec
|
File details
Details for the file cjm_fasthtml_job_monitor-0.0.2-py3-none-any.whl.
File metadata
- Download URL: cjm_fasthtml_job_monitor-0.0.2-py3-none-any.whl
- Upload date:
- Size: 31.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89c9f11cb858e376fc8df6ca20fd732f846d3df278b1cf1914a8f26af0595417
|
|
| MD5 |
105368130da43d5856e93d6aa49ac91a
|
|
| BLAKE2b-256 |
fcf8984b7ed70f8af099c1fa46327c2f8e7b318ffbf3e505472bfd8bf085a28a
|