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
|
2024-12-15 14:40:03 +01:00
|
|
|
import uvicorn
|
2025-01-22 23:47:28 +01:00
|
|
|
from fasthtml.common import FileResponse, JSONResponse
|
2025-06-10 22:00:28 +02:00
|
|
|
from loguru import logger
|
2025-01-22 23:47:28 +01:00
|
|
|
from monsterui.core import FastHTML, Theme
|
2024-12-15 14:40:03 +01:00
|
|
|
|
|
|
|
from akkudoktoreos.config.config import get_config
|
|
|
|
|
2025-01-22 23:47:28 +01:00
|
|
|
# Pages
|
2025-03-27 21:53:01 +01:00
|
|
|
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
|
|
|
|
|
2024-12-15 14:40:03 +01:00
|
|
|
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
|
2024-12-15 14:40:03 +01:00
|
|
|
|
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()
|
2025-03-27 21:53:01 +01:00
|
|
|
# Add Bokeh headers
|
2025-01-22 23:47:28 +01:00
|
|
|
hdrs = (
|
|
|
|
Theme.green.headers(highlightjs=True),
|
|
|
|
BokehJS,
|
|
|
|
)
|
|
|
|
|
|
|
|
# The EOSdash application
|
|
|
|
app: FastHTML = FastHTML(
|
|
|
|
title="EOSdash",
|
|
|
|
hdrs=hdrs,
|
|
|
|
secret_key=os.getenv("EOS_SERVER__EOSDASH_SESSKEY"),
|
|
|
|
)
|
2025-01-18 14:07:08 +01:00
|
|
|
|
|
|
|
|
2025-01-22 23:47:28 +01:00
|
|
|
def eos_server() -> tuple[str, int]:
|
|
|
|
"""Retrieves the EOS server host and port configuration.
|
2025-01-18 14:07:08 +01:00
|
|
|
|
2025-01-22 23:47:28 +01:00
|
|
|
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
|
2025-01-18 14:07:08 +01:00
|
|
|
else:
|
2025-01-22 23:47:28 +01:00
|
|
|
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
|
2024-12-15 14:40:03 +01:00
|
|
|
|
2025-01-22 23:47:28 +01:00
|
|
|
return eos_host, eos_port
|
|
|
|
|
|
|
|
|
|
|
|
@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",
|
2025-03-27 21:53:01 +01:00
|
|
|
"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()
|
2024-12-15 14:40:03 +01:00
|
|
|
|
|
|
|
|
2025-03-27 21:53:01 +01:00
|
|
|
@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.
|
2024-12-15 14:40:03 +01:00
|
|
|
|
2025-01-22 23:47:28 +01:00
|
|
|
Returns:
|
|
|
|
Configuration: The Configuration page component.
|
|
|
|
"""
|
|
|
|
return Configuration(*eos_server())
|
|
|
|
|
|
|
|
|
2025-03-27 21:53:01 +01:00
|
|
|
@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())
|
2024-12-15 14:40:03 +01:00
|
|
|
|
|
|
|
|
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
|
|
|
|
2024-12-15 14:40:03 +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,
|
2024-12-15 14:40:03 +01:00
|
|
|
)
|
|
|
|
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()
|