Files
EOS/src/akkudoktoreos/server/eosdash.py
T

340 lines
9.9 KiB
Python
Raw Normal View History

2025-01-21 19:20:50 +01:00
import argparse
2025-01-24 00:09:28 +01:00
import os
2025-01-18 14:26:34 +01:00
import sys
2025-01-22 23:47:28 +01:00
import traceback
from pathlib import Path
from typing import Optional
2025-01-21 19:20:50 +01:00
2025-02-12 21:35:51 +01:00
import psutil
import uvicorn
2025-01-22 23:47:28 +01:00
from fasthtml.common import FileResponse, JSONResponse
from loguru import logger
2025-01-22 23:47:28 +01:00
from monsterui.core import FastHTML, Theme
from akkudoktoreos.config.config import get_config
2025-01-22 23:47:28 +01:00
# Pages
from akkudoktoreos.server.dash.admin import Admin
from akkudoktoreos.server.dash.bokeh import BokehJS
from akkudoktoreos.server.dash.components import Page
from akkudoktoreos.server.dash.configuration import ConfigKeyUpdate, Configuration
2025-01-22 23:47:28 +01:00
from akkudoktoreos.server.dash.demo import Demo
from akkudoktoreos.server.dash.footer import Footer
from akkudoktoreos.server.dash.hello import Hello
from akkudoktoreos.server.server import get_default_host, wait_for_port_free
config_eos = get_config()
2025-01-22 23:47:28 +01:00
# The favicon for EOSdash
favicon_filepath = Path(__file__).parent.joinpath("dash/assets/favicon/favicon.ico")
if not favicon_filepath.exists():
raise ValueError(f"Does not exist {favicon_filepath}")
2025-01-21 19:20:50 +01:00
# Command line arguments
2025-01-22 23:47:28 +01:00
args: Optional[argparse.Namespace] = None
2025-01-18 14:07:08 +01:00
2025-01-22 23:47:28 +01:00
# Get frankenui and tailwind headers via CDN using Theme.green.headers()
# Add Bokeh headers
2025-01-22 23:47:28 +01:00
hdrs = (
Theme.green.headers(highlightjs=True),
BokehJS,
)
2025-01-18 14:07:08 +01:00
2025-01-22 23:47:28 +01:00
# The EOSdash application
app: FastHTML = FastHTML(
title="EOSdash",
hdrs=hdrs,
2025-01-19 13:00:53 +01:00
secret_key=os.getenv("EOS_SERVER__EOSDASH_SESSKEY"),
)
2025-01-22 23:47:28 +01:00
def eos_server() -> tuple[str, int]:
"""Retrieves the EOS server host and port configuration.
If `args` is provided, it uses the `eos_host` and `eos_port` from `args`.
Otherwise, it falls back to the values from `config_eos.server`.
Returns:
tuple[str, int]: A tuple containing:
- `eos_host` (str): The EOS server hostname or IP.
- `eos_port` (int): The EOS server port.
"""
if args is None:
eos_host = str(config_eos.server.host)
eos_port = config_eos.server.port
else:
eos_host = args.eos_host
eos_port = args.eos_port
eos_host = eos_host if eos_host else get_default_host()
eos_port = eos_port if eos_port else 8503
return eos_host, eos_port
2025-01-22 23:47:28 +01:00
@app.get("/favicon.ico")
def get_eosdash_favicon(): # type: ignore
"""Get favicon."""
return FileResponse(path=favicon_filepath)
@app.get("/")
def get_eosdash(): # type: ignore
"""Serves the main EOSdash page.
Returns:
Page: The main dashboard page with navigation links and footer.
"""
return Page(
None,
{
"EOSdash": "/eosdash/hello",
"Config": "/eosdash/configuration",
"Demo": "/eosdash/demo",
"Admin": "/eosdash/admin",
2025-01-22 23:47:28 +01:00
},
Hello(),
Footer(*eos_server()),
"/eosdash/footer",
)
@app.get("/eosdash/footer")
def get_eosdash_footer(): # type: ignore
"""Serves the EOSdash Foooter information.
Returns:
Footer: The Footer component.
"""
return Footer(*eos_server())
@app.get("/eosdash/hello")
def get_eosdash_hello(): # type: ignore
"""Serves the EOSdash Hello page.
Returns:
Hello: The Hello page component.
"""
return Hello()
@app.get("/eosdash/admin")
def get_eosdash_admin(): # type: ignore
"""Serves the EOSdash Admin page.
Returns:
Admin: The Admin page component.
"""
return Admin(*eos_server())
@app.post("/eosdash/admin")
def post_eosdash_admin(data: dict): # type: ignore
return Admin(*eos_server(), data)
2025-01-22 23:47:28 +01:00
@app.get("/eosdash/configuration")
def get_eosdash_configuration(): # type: ignore
"""Serves the EOSdash Configuration page.
Returns:
Configuration: The Configuration page component.
"""
return Configuration(*eos_server())
@app.put("/eosdash/configuration")
def put_eosdash_configuration(data: dict): # type: ignore
return ConfigKeyUpdate(*eos_server(), data["key"], data["value"])
2025-01-22 23:47:28 +01:00
@app.get("/eosdash/demo")
def get_eosdash_demo(): # type: ignore
"""Serves the EOSdash Demo page.
Returns:
Demo: The Demo page component.
"""
return Demo(*eos_server())
2025-02-12 21:35:51 +01:00
@app.get("/eosdash/health")
def get_eosdash_health(): # type: ignore
"""Health check endpoint to verify that the EOSdash server is alive."""
return JSONResponse(
{
"status": "alive",
"pid": psutil.Process().pid,
}
)
2025-01-22 23:47:28 +01:00
@app.get("/eosdash/assets/{fname:path}.{ext:static}")
def get_eosdash_assets(fname: str, ext: str): # type: ignore
"""Get assets."""
asset_filepath = Path(__file__).parent.joinpath(f"dash/assets/{fname}.{ext}")
return FileResponse(path=asset_filepath)
def run_eosdash() -> None:
2025-01-21 19:20:50 +01:00
"""Run the EOSdash server with the specified configurations.
This function starts the EOSdash server using the Uvicorn ASGI server. It accepts
arguments for the host, port, log level, access log, and reload options. The
log level is converted to lowercase to ensure compatibility with Uvicorn's
expected log level format. If an error occurs while attempting to bind the
server to the specified host and port, an error message is logged and the
application exits.
Returns:
2025-02-12 21:35:51 +01:00
None
2025-01-21 19:20:50 +01:00
"""
2025-01-22 23:47:28 +01:00
# Setup parameters from args, config_eos and default
# Remember parameters that are also in config
# - EOS host
if args and args.eos_host:
eos_host = args.eos_host
elif config_eos.server.host:
eos_host = config_eos.server.host
else:
eos_host = get_default_host()
config_eos.server.host = eos_host
# - EOS port
if args and args.eos_port:
eos_port = args.eos_port
elif config_eos.server.port:
eos_port = config_eos.server.port
else:
eos_port = 8503
config_eos.server.port = eos_port
# - EOSdash host
if args and args.host:
eosdash_host = args.host
elif config_eos.server.eosdash.host:
eosdash_host = config_eos.server.eosdash_host
else:
eosdash_host = get_default_host()
config_eos.server.eosdash_host = eosdash_host
# - EOS port
if args and args.port:
eosdash_port = args.port
elif config_eos.server.eosdash_port:
eosdash_port = config_eos.server.eosdash_port
else:
eosdash_port = 8504
config_eos.server.eosdash_port = eosdash_port
# - log level
if args and args.log_level:
log_level = args.log_level
else:
log_level = "info"
# - access log
if args and args.access_log:
access_log = args.access_log
else:
access_log = False
# - reload
if args and args.reload:
reload = args.reload
else:
reload = False
2025-01-24 00:09:28 +01:00
# Make hostname Windows friendly
2025-06-03 08:30:37 +02:00
if eosdash_host == "0.0.0.0" and os.name == "nt": # noqa: S104
2025-01-22 23:47:28 +01:00
eosdash_host = "localhost"
2025-02-12 21:35:51 +01:00
# Wait for EOSdash port to be free - e.g. in case of restart
2025-01-22 23:47:28 +01:00
wait_for_port_free(eosdash_port, timeout=120, waiting_app_name="EOSdash")
2025-02-12 21:35:51 +01:00
try:
uvicorn.run(
2025-01-21 19:20:50 +01:00
"akkudoktoreos.server.eosdash:app",
2025-01-22 23:47:28 +01:00
host=eosdash_host,
port=eosdash_port,
log_level=log_level.lower(),
2025-01-21 19:20:50 +01:00
access_log=access_log,
reload=reload,
)
except Exception as e:
2025-01-22 23:47:28 +01:00
logger.error(f"Could not bind to host {eosdash_host}:{eosdash_port}. Error: {e}")
2025-01-21 19:20:50 +01:00
raise e
def main() -> None:
"""Parse command-line arguments and start the EOSdash server with the specified options.
This function sets up the argument parser to accept command-line arguments for
host, port, log_level, access_log, and reload. It uses default values from the
2025-01-22 23:47:28 +01:00
config module if arguments are not provided. After parsing the arguments,
2025-01-21 19:20:50 +01:00
it starts the EOSdash server with the specified configurations.
Command-line Arguments:
2025-01-18 14:26:34 +01:00
--host (str): Host for the EOSdash server (default: value from config).
--port (int): Port for the EOSdash server (default: value from config).
--eos-host (str): Host for the EOS server (default: value from config).
--eos-port (int): Port for the EOS server (default: value from config).
2025-01-21 19:20:50 +01:00
--log_level (str): Log level for the server. Options: "critical", "error", "warning", "info", "debug", "trace" (default: "info").
--access_log (bool): Enable or disable access log. Options: True or False (default: False).
--reload (bool): Enable or disable auto-reload. Useful for development. Options: True or False (default: False).
"""
parser = argparse.ArgumentParser(description="Start EOSdash server.")
parser.add_argument(
"--host",
type=str,
2025-01-18 14:26:34 +01:00
default=str(config_eos.server.eosdash_host),
help="Host for the EOSdash server (default: value from config)",
2025-01-21 19:20:50 +01:00
)
parser.add_argument(
"--port",
type=int,
2025-01-18 14:26:34 +01:00
default=config_eos.server.eosdash_port,
help="Port for the EOSdash server (default: value from config)",
2025-01-21 19:20:50 +01:00
)
parser.add_argument(
"--eos-host",
type=str,
2025-01-18 14:26:34 +01:00
default=str(config_eos.server.host),
2025-02-12 21:35:51 +01:00
help="Host of the EOS server (default: value from config)",
2025-01-21 19:20:50 +01:00
)
parser.add_argument(
"--eos-port",
type=int,
2025-01-18 14:26:34 +01:00
default=config_eos.server.port,
2025-02-12 21:35:51 +01:00
help="Port of the EOS server (default: value from config)",
2025-01-21 19:20:50 +01:00
)
parser.add_argument(
"--log_level",
type=str,
default="info",
help='Log level for the server. Options: "critical", "error", "warning", "info", "debug", "trace" (default: "info")',
)
parser.add_argument(
"--access_log",
type=bool,
default=False,
2025-01-22 23:47:28 +01:00
help="Enable or disable access log. Options: True or False (default: False)",
2025-01-21 19:20:50 +01:00
)
parser.add_argument(
"--reload",
type=bool,
default=False,
help="Enable or disable auto-reload. Useful for development. Options: True or False (default: False)",
)
2025-01-22 23:47:28 +01:00
global args
2025-01-21 19:20:50 +01:00
args = parser.parse_args()
try:
2025-01-22 23:47:28 +01:00
run_eosdash()
2025-02-12 21:35:51 +01:00
except Exception as ex:
error_msg = f"Failed to run EOSdash: {ex}"
logger.error(error_msg)
2025-01-22 23:47:28 +01:00
traceback.print_exc()
2025-01-18 14:26:34 +01:00
sys.exit(1)
2025-01-21 19:20:50 +01:00
if __name__ == "__main__":
main()