Skip to content

Commit

Permalink
orfoehowhijo
Browse files Browse the repository at this point in the history
Signed-off-by: Bobby Noelte <[email protected]>
  • Loading branch information
b0661 committed Jan 26, 2025
1 parent c4798e9 commit e36c529
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 48 deletions.
16 changes: 16 additions & 0 deletions docs/_generated/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,22 @@ Returns:

---

## GET /v1/health

**Links**: [local](http://localhost:8503/docs#/default/fastapi_health_get_v1_health_get), [eos](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_health_get_v1_health_get)

Fastapi Health Get

```
Health check endpoint to verify that the server is alive.
```

**Responses**:

- **200**: Successful Response

---

## PUT /v1/measurement/data

**Links**: [local](http://localhost:8503/docs#/default/fastapi_measurement_data_put_v1_measurement_data_put), [eos](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_measurement_data_put_v1_measurement_data_put)
Expand Down
27 changes: 25 additions & 2 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3160,7 +3160,10 @@
"description": "Successful Response"
}
},
"summary": "Fastapi Config Update Post"
"summary": "Fastapi Config Update Post",
"tags": [
"config"
]
}
},
"/v1/config/value": {
Expand Down Expand Up @@ -3212,7 +3215,27 @@
"description": "Validation Error"
}
},
"summary": "Fastapi Config Value Put"
"summary": "Fastapi Config Value Put",
"tags": [
"config"
]
}
},
"/v1/health": {
"get": {
"description": "Health check endpoint to verify that the server is alive.",
"operationId": "fastapi_health_get_v1_health_get",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
}
},
"summary": "Fastapi Health Get"
}
},
"/v1/measurement/data": {
Expand Down
38 changes: 25 additions & 13 deletions src/akkudoktoreos/server/dash/components.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Optional, Union

from fasthtml.common import FT, H1, Div, Li, P
from fasthtml.common import FT, H1, Div, Li
from monsterui.foundations import stringify
from monsterui.franken import (
Button,
Expand Down Expand Up @@ -64,7 +64,7 @@ def Markdown(md: str) -> FT:
return render_md(md)


def Header(title: Optional[str]) -> Div:
def DashboardHeader(title: Optional[str]) -> Div:
"""Creates a styled header with a title.
Args:
Expand All @@ -78,16 +78,24 @@ def Header(title: Optional[str]) -> Div:
return Div(H1(title, cls="text-2xl font-bold mb-4"), cls="header")


def Footer(info: str) -> Card:
def DashboardFooter(path: str) -> Card:
"""Creates a styled footer with the provided information.
The footer content is reloaded every 5 seconds from path.
Args:
info (str): Footer information text.
path (str): Path to reload footer content from
Returns:
Card: A styled `Card` element containing the footer.
"""
return Card(Container(P(info, cls="text-sm font-medium"), cls="footer"))
return Card(
Container("Footer", id="footer-content"),
hx_get=f"{path}",
hx_trigger="every 5s",
hx_target="#footer-content",
hx_swap="innerHTML",
)


def DashboardTrigger(*c: Any, cls: Optional[Union[str, tuple]] = None, **kwargs: Any) -> Button:
Expand Down Expand Up @@ -129,10 +137,10 @@ def DashboardTabs(dashboard_items: dict[str, str]) -> Card:
)
for menu, path in dashboard_items.items()
]
return Card(TabContainer(*dash_items), alt=True)
return Card(TabContainer(*dash_items, cls="gap-4"), alt=True)


def Content(content: Any) -> Card:
def DashboardContent(content: Any) -> Card:
"""Creates a content section within a styled card.
Args:
Expand All @@ -141,27 +149,31 @@ def Content(content: Any) -> Card:
Returns:
Card: A styled `Card` element containing the content.
"""
return Card(Container(content, id="page-content"))
return Card(ScrollArea(Container(content, id="page-content"), cls="h-[75vh] w-full rounded-md"))


def Page(
title: Optional[str], dashboard_items: dict[str, str], content: Any, footer_info: str
title: Optional[str],
dashboard_items: dict[str, str],
content: Any,
footer_path: str,
) -> Div:
"""Generates a full-page layout with a header, dashboard items, content, and footer.
Args:
title (Optional[str]): The page title.
dashboard_items (dict[str, str]): A dictionary of dashboard items.
content (Any): The main content for the page.
footer_info (str): Footer information text.
footer_content (Any): Footer content.
footer_path (Any): Path to reload footer content from.
Returns:
Div: A `Div` element representing the entire page layout.
"""
return Container(
Header(title),
DashboardHeader(title),
DashboardTabs(dashboard_items),
Content(content),
Footer(footer_info),
DashboardContent(content),
DashboardFooter(footer_path),
cls=("w-screen p-4 space-y-4", ContainerT.xl),
)
50 changes: 26 additions & 24 deletions src/akkudoktoreos/server/dash/configuration.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
from functools import reduce
from http import HTTPStatus
from typing import Any, Dict, List, Optional, TypeVar, Union
from typing import Any, Dict, List, Optional, Sequence, TypeVar, Union

import requests
from monsterui.franken import Table, Tbody, Td, Th, Thead, Tr
Expand All @@ -12,7 +11,6 @@
from akkudoktoreos.config.config import get_config
from akkudoktoreos.core.logging import get_logger
from akkudoktoreos.core.pydantic import PydanticBaseModel
from akkudoktoreos.server.dash.components import ScrollArea

logger = get_logger(__name__)
config_eos = get_config()
Expand All @@ -21,37 +19,45 @@


def get_nested_value(
dictionary: Dict[str, Any], keys: List[str], default: Optional[T] = None
dictionary: Union[Dict[str, Any], List[Any]],
keys: Sequence[Union[str, int]],
default: Optional[T] = None,
) -> Union[Any, T]:
"""Retrieve a nested value from a dictionary using a list of keys.
"""Retrieve a nested value from a dictionary or list using a sequence of keys.
Args:
dictionary (Dict[str, Any]): The nested dictionary to search.
keys (List[str]): A list of keys representing the path to the desired value.
dictionary (Union[Dict[str, Any], List[Any]]): The nested dictionary or list to search.
keys (Sequence[Union[str, int]]): A sequence of keys or indices representing the path to the desired value.
default (Optional[T]): A value to return if the path is not found.
Returns:
Union[Any, T]: The value at the specified nested path, or the default value if not found.
Raises:
TypeError: If the dictionary is not of type `dict` or keys is not a `list`.
TypeError: If the input is not a dictionary or list, or if keys are not a sequence.
KeyError: If a key is not found in a dictionary.
IndexError: If an index is out of range in a list.
"""
# Validate input type
if not isinstance(dictionary, dict):
raise TypeError("First argument must be a dictionary")
if not isinstance(dictionary, (dict, list)):
raise TypeError("The first argument must be a dictionary or list")
if not isinstance(keys, Sequence):
raise TypeError("Keys must be provided as a sequence (e.g., list, tuple)")

# Validate keys input
if not isinstance(keys, list):
raise TypeError("Keys must be provided as a list")

# Empty key list returns the entire dictionary
if not keys:
return dictionary

try:
# Use reduce for a functional approach
return reduce(lambda d, key: d[key], keys, dictionary)
except (KeyError, TypeError):
# Traverse the structure
current = dictionary
for key in keys:
if isinstance(current, dict) and isinstance(key, str):
current = current[key]
elif isinstance(current, list) and isinstance(key, int):
current = current[key]
else:
raise KeyError(f"Invalid key or index: {key}")
return current
except (KeyError, IndexError, TypeError):
return default


Expand Down Expand Up @@ -177,7 +183,6 @@ def get_configuration(eos_host: Optional[str], eos_port: Optional[Union[str, int
Returns:
List[dict]: A list of processed configuration entries.
"""
config_eos = get_config()
if eos_host is None:
eos_host = config_eos.server.host
if eos_port is None:
Expand Down Expand Up @@ -237,7 +242,4 @@ def Configuration(eos_host: Optional[str], eos_port: Optional[Union[str, int]])
for config in get_configuration(eos_host, eos_port)
]
head = Thead(*map(Th, flds), cls="bg-lime-400 text-left")
return ScrollArea(
Table(head, Tbody(*rows), cls="w-full"),
cls="h-[75vh] w-full rounded-md",
)
return Table(head, Tbody(*rows), cls="w-full")
7 changes: 2 additions & 5 deletions src/akkudoktoreos/server/dash/hello.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fasthtml.common import Div

from akkudoktoreos.server.dash.components import Markdown, ScrollArea
from akkudoktoreos.server.dash.components import Markdown

hello_md = """# Akkudoktor EOSdash
Expand All @@ -17,7 +17,4 @@


def Hello() -> Div:
return ScrollArea(
Markdown(hello_md),
cls="h-[75vh] w-full rounded-md",
)
return Markdown(hello_md)
12 changes: 9 additions & 3 deletions src/akkudoktoreos/server/eos.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import httpx
import uvicorn
from fastapi import FastAPI, Query, Request
from fastapi import FastAPI, Query, Request, status
from fastapi.exceptions import HTTPException
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse, Response

Expand Down Expand Up @@ -144,7 +144,13 @@ class PdfResponse(FileResponse):
media_type = "application/pdf"


@app.put("/v1/config/value")
@app.get("/v1/health", status_code=status.HTTP_200_OK)
async def fastapi_health_get(): # type: ignore
"""Health check endpoint to verify that the server is alive."""
return {"status": "alive"}


@app.put("/v1/config/value", tags=["config"])
def fastapi_config_value_put(
key: Annotated[str, Query(description="configuration key")],
value: Annotated[Any, Query(description="configuration value")],
Expand All @@ -169,7 +175,7 @@ def fastapi_config_value_put(
return config_eos


@app.post("/v1/config/update")
@app.post("/v1/config/update", tags=["config"])
def fastapi_config_update_post() -> ConfigEOS:
"""Reset the configuration to the EOS configuration file.
Expand Down
19 changes: 18 additions & 1 deletion src/akkudoktoreos/server/eosdash.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# Pages
from akkudoktoreos.server.dash.configuration import Configuration
from akkudoktoreos.server.dash.demo import Demo
from akkudoktoreos.server.dash.footer import Footer
from akkudoktoreos.server.dash.hello import Hello

logger = get_logger(__name__)
Expand Down Expand Up @@ -44,10 +45,26 @@ def get_eosdash(): # type: ignore
"Demo": "/eosdash/demo",
},
Hello(),
"Footer_Info",
"/eosdash/footer",
)


@app.get("/eosdash/footer")
def get_eosdash_footer(): # type: ignore
"""Serves the EOSdash Foooter information.
Returns:
Footer: The Footer component.
"""
if args is None:
eos_host = None
eos_port = None
else:
eos_host = args.eos_host
eos_port = args.eos_port
return Footer(eos_host, eos_port)


@app.get("/eosdash/hello")
def get_eosdash_hello(): # type: ignore
"""Serves the EOSdash Hello page.
Expand Down

0 comments on commit e36c529

Please sign in to comment.