Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion lau_dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import dash_bootstrap_components as dbc
from laue_portal.database.session_utils import init_db, get_engine
import laue_portal.database.db_utils as db_utils
from laue_portal.processing.redis_utils import init_redis_status
import os
import config
import logging
Expand Down Expand Up @@ -29,8 +30,12 @@ def ensure_database_exists():
logging.info(f"Database file '{db_path}' already exists. Running on existing database.")


# Initialize Redis status BEFORE creating the Dash app
# This ensures the status is set before pages (and navbar) are imported
init_redis_status()

app = dash.Dash(__name__,
external_stylesheets=[dbc.themes.FLATLY, dbc_css],
external_stylesheets=[dbc.themes.FLATLY, dbc_css, dbc.icons.BOOTSTRAP],
suppress_callback_exceptions=True,
pages_folder="laue_portal/pages",)

Expand Down
55 changes: 14 additions & 41 deletions laue_portal/components/navbar.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,25 @@
# import dash_bootstrap_components as dbc

# navbar = dbc.NavbarSimple(
# children=[
# dbc.NavItem(dbc.NavLink("Scans", href="/", active="exact")),
# dbc.NavItem(dbc.NavLink("Mask Reconstructions", href="/reconstructions", active="exact")),
# dbc.NavItem(dbc.NavLink("Wire Reconstructions", href="/wire-reconstructions", active="exact")),
# dbc.NavItem(dbc.NavLink("Peak Indexings", href="/peakindexings", active="exact")),
# dbc.NavItem(dbc.NavLink("Run Monitor", href="/run-monitor", active="exact")),
# dbc.DropdownMenu(
# id="manual-entry-dropdown",
# children=[
# dbc.DropdownMenuItem("New Scan", href="/create-scan"),
# dbc.DropdownMenuItem("New CA Reconstruction", href="/create-reconstruction"),
# dbc.DropdownMenuItem("New Wire Reconstruction", href="/create-wire-reconstruction"),
# dbc.DropdownMenuItem("New Peak Indexing", href="/create-peakindexing"),
# ],
# nav=True,
# in_navbar=True,
# label="Manual Entry",
# ),
# ],
# brand="3DMN Portal",
# brand_href="/",
# color="primary",
# className="navbar-lg",
# dark=True,
# style={"max-height": "50px"},
# )




import dash_bootstrap_components as dbc
from dash import html, Input, Output, State, callback
from dash import html, Input, Output, State, callback, dcc
from laue_portal.processing.redis_utils import REDIS_CONNECTED_AT_STARTUP

navbar = dbc.Navbar(
dbc.Container(
[
dbc.NavbarBrand("3DMN Portal", href="/"),
dbc.NavbarBrand("3DMN Portal", href="/", id="navbar-brand"),
html.Div([
html.I(
className="bi bi-hdd-network",
style={
'fontSize': '1.5rem',
'color': '#18BC9C' if REDIS_CONNECTED_AT_STARTUP else '#FF6B6B'
}
),
], className="d-flex align-items-center ms-2"),
dbc.NavbarToggler(id="nav-toggler"),
dbc.Collapse(
dbc.Nav(
[
dbc.NavItem(dbc.NavLink("Scans", href="/", active="exact")),
dbc.NavItem(dbc.NavLink("Scans", href="/scans", active="exact")),
dbc.NavItem(dbc.NavLink("Mask Reconstructions", href="/reconstructions", active="exact")),
dbc.NavItem(dbc.NavLink("Wire Reconstructions", href="/wire-reconstructions", active="exact")),
dbc.NavItem(dbc.NavLink("Indexations", href="/peakindexings", active="exact")),
Expand Down Expand Up @@ -73,12 +50,8 @@
fluid=True,
),
className="py-3",
#brand="3DMN Portal",
#brand_href="/",
color="primary",
dark=True,
#className="navbar-lg",
#style={"max-height": "70px"},
)

@callback(
Expand All @@ -88,4 +61,4 @@
prevent_initial_call=True,
)
def toggle_nav(n, is_open):
return not is_open
return not is_open
4 changes: 2 additions & 2 deletions laue_portal/pages/scans.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from laue_portal.pages.scan import build_technique_strings
import laue_portal.database.session_utils as session_utils

dash.register_page(__name__, path='/')
dash.register_page(__name__, path='/scans')

layout = html.Div([
navbar.navbar,
Expand Down Expand Up @@ -212,7 +212,7 @@ def _get_metadatas():
prevent_initial_call=True,
)
def get_metadatas(path):
if path == '/':
if path == '/scans':
cols, metadatas_records = _get_metadatas()
return cols, metadatas_records
else:
Expand Down
227 changes: 227 additions & 0 deletions laue_portal/pages/status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import dash
from dash import html, dcc
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import laue_portal.components.navbar as navbar
from laue_portal.processing import redis_utils
import config
import os

dash.register_page(__name__, path='/')

# Build the layout
layout = html.Div([
navbar.navbar,
dbc.Container([
# Auto-refresh interval (every 5 seconds)
dcc.Interval(
id='status-refresh-interval',
interval=5*1000, # in milliseconds
n_intervals=0
),

# Row 1: Welcome and Connection Status
dbc.Row([
# Welcome Card
dbc.Col([
dbc.Card([
dbc.CardHeader(html.H4("Welcome to the 3DMN Laue Portal", className="mb-0")),
dbc.CardBody([
html.P([
"The 3DMN Laue Portal is a data processing platform for ",
"Laue X-ray diffraction experiments at beamline 34-ID-E."
], className="mb-3"),
html.H6("Capabilities:", className="mb-2"),
html.Ul([
html.Li("Scan management and tracking"),
html.Li("Wire-based depth reconstruction"),
html.Li("Coded aperture reconstruction"),
html.Li("Automated peak indexing"),
html.Li("Distributed job queue"),
], className="mb-0"),
])
], className="h-100")
], md=6, className="mb-4"),

# Connection Status Card
dbc.Col([
dbc.Card([
dbc.CardHeader(html.H4("Connection Status", className="mb-0")),
dbc.CardBody([
html.Div(id='connection-status-content')
])
], className="h-100")
], md=6, className="mb-4"),
]),

# Row 2: System Resources and Quick Actions
dbc.Row([
# System Resources Card
dbc.Col([
dbc.Card([
dbc.CardHeader(html.H4("System Resources", className="mb-0")),
dbc.CardBody([
html.Div(id='system-resources-content')
])
], className="h-100")
], md=6, className="mb-4"),

# Quick Actions Card
dbc.Col([
dbc.Card([
dbc.CardHeader(html.H4("Quick Actions", className="mb-0")),
dbc.CardBody([
dbc.ListGroup([
dbc.ListGroupItem([
html.I(className="bi bi-list-ul me-2"),
"View All Scans"
], href="/scans", action=True, className="d-flex align-items-center"),
dbc.ListGroupItem([
html.I(className="bi bi-gear me-2"),
"Create Wire Reconstruction"
], href="/create-wire-reconstruction", action=True, className="d-flex align-items-center"),
dbc.ListGroupItem([
html.I(className="bi bi-grid-3x3 me-2"),
"Create Coded Aperture Reconstruction"
], href="/create-reconstruction", action=True, className="d-flex align-items-center"),
dbc.ListGroupItem([
html.I(className="bi bi-search me-2"),
"Create Peak Indexing"
], href="/create-peakindexing", action=True, className="d-flex align-items-center"),
dbc.ListGroupItem([
html.I(className="bi bi-activity me-2"),
"Monitor Job Queue"
], href="/run-monitor", action=True, className="d-flex align-items-center"),
], flush=True)
])
], className="h-100")
], md=6, className="mb-4"),
]),

], fluid=True, className="mt-4")
])


# Callback to update connection status
@dash.callback(
Output('connection-status-content', 'children'),
Input('status-refresh-interval', 'n_intervals')
)
def update_connection_status(n):
"""Update the connection status card with current system information."""

# Check Redis connection
redis_connected = redis_utils.check_redis_connection()
redis_startup_status = redis_utils.REDIS_CONNECTED_AT_STARTUP

# Database path
db_path = config.db_file
db_exists = os.path.exists(db_path)

# Server info
dash_url = f"http://{config.DASH_CONFIG['host']}:{config.DASH_CONFIG['port']}"
redis_url = f"{config.REDIS_CONFIG['host']}:{config.REDIS_CONFIG['port']}"

return [
# Database Status
html.Div([
html.Strong("Database: "),
dbc.Badge(
"Connected" if db_exists else "Not Found",
color="success" if db_exists else "danger",
className="me-2"
),
html.Br(),
html.Small(db_path, className="text-muted"),
], className="mb-3"),

# Dash Server Status
html.Div([
html.Strong("Dash Server: "),
dbc.Badge("Running", color="success", className="me-2"),
html.Br(),
html.Small(dash_url, className="text-muted"),
], className="mb-3"),

# Redis Status
html.Div([
html.Strong("Redis Queue: "),
dbc.Badge(
"Connected" if redis_connected else "Disconnected",
color="success" if redis_connected else "danger",
className="me-2"
),
html.Br(),
html.Small(redis_url, className="text-muted"),
html.Br(),
html.Small(
f"Startup status: {'Connected' if redis_startup_status else 'Disconnected'}"
if redis_startup_status is not None else "Startup status: Unknown",
className="text-muted fst-italic"
),
], className="mb-0"),
]


# Callback to update system resources
@dash.callback(
Output('system-resources-content', 'children'),
Input('status-refresh-interval', 'n_intervals')
)
def update_system_resources(n):
"""Update the system resources card with queue and worker information."""

try:
# Get queue statistics
queue_stats = redis_utils.get_queue_stats()

# Get worker information
workers_info = redis_utils.get_workers_info()

return [
# Queue Statistics
html.H6("Job Queue:", className="mb-2"),
dbc.Row([
dbc.Col([
html.Div([
html.H3(queue_stats.get('queued', 0), className="mb-0 text-primary"),
html.Small("Queued", className="text-muted")
], className="text-center")
], width=6),
dbc.Col([
html.Div([
html.H3(queue_stats.get('started', 0), className="mb-0 text-info"),
html.Small("Running", className="text-muted")
], className="text-center")
], width=6),
], className="mb-3"),

html.Hr(),

# Worker Information
html.H6("Workers:", className="mb-2"),
html.Div([
dbc.Badge(
f"{len(workers_info)} Active" if workers_info else "No Workers",
color="success" if workers_info else "warning",
className="me-2"
),
html.Br() if workers_info else None,
html.Div([
html.Div([
html.Small([
html.Strong(f"{worker['name']}: "),
f"{worker['state']}"
])
], className="mb-1") for worker in workers_info
] if workers_info else [], className="mt-2")
], className="mb-0"),
]

except Exception as e:
return [
dbc.Alert([
html.I(className="bi bi-exclamation-triangle me-2"),
"No system data. Redis is not connected."
], color="warning", className="mb-0")
]
20 changes: 19 additions & 1 deletion laue_portal/processing/redis_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from redis import Redis
import redis
from rq import Queue, Worker
from rq.job import Job, Dependency
from rq.registry import StartedJobRegistry, FinishedJobRegistry, FailedJobRegistry
Expand All @@ -26,11 +27,28 @@
logger = logging.getLogger(__name__)

# Redis connection
redis_conn = Redis(host='localhost', port=6379, decode_responses=False)
redis_conn = Redis(host='localhost', port=REDIS_CONFIG['port'], decode_responses=False)

# Single queue for all job types
job_queue = Queue('laue_jobs', connection=redis_conn)

# Global variable to store startup status
REDIS_CONNECTED_AT_STARTUP = None

def check_redis_connection():
"""Check if Redis server is accessible and responding."""
try:
return redis_conn.ping()
except (redis.ConnectionError, redis.TimeoutError, Exception):
return False

def init_redis_status():
"""Initialize Redis status check on startup."""
global REDIS_CONNECTED_AT_STARTUP
REDIS_CONNECTED_AT_STARTUP = check_redis_connection()
logger.info(f"Redis connection status at startup: {'Connected' if REDIS_CONNECTED_AT_STARTUP else 'Disconnected'}")
return REDIS_CONNECTED_AT_STARTUP

# Job status mapping
STATUS_MAPPING = {
0: "Queued",
Expand Down
Loading