2025-01-22 23:47:28 +01:00
|
|
|
from typing import Any, Optional, Union
|
|
|
|
|
|
|
|
from fasthtml.common import H1, Div, Li
|
2025-04-05 13:08:12 +02:00
|
|
|
from monsterui.daisy import (
|
|
|
|
Alert,
|
|
|
|
AlertT,
|
|
|
|
)
|
2025-01-22 23:47:28 +01:00
|
|
|
from monsterui.foundations import stringify
|
|
|
|
from monsterui.franken import (
|
2025-04-05 13:08:12 +02:00
|
|
|
H3,
|
2025-01-22 23:47:28 +01:00
|
|
|
Button,
|
|
|
|
ButtonT,
|
|
|
|
Card,
|
|
|
|
Container,
|
|
|
|
ContainerT,
|
|
|
|
Details,
|
|
|
|
DivLAligned,
|
|
|
|
DivRAligned,
|
2025-03-27 21:53:01 +01:00
|
|
|
Form,
|
2025-01-22 23:47:28 +01:00
|
|
|
Grid,
|
|
|
|
Input,
|
|
|
|
P,
|
|
|
|
Summary,
|
|
|
|
TabContainer,
|
|
|
|
UkIcon,
|
|
|
|
)
|
|
|
|
|
|
|
|
scrollbar_viewport_styles = (
|
|
|
|
"scrollbar-width: none; -ms-overflow-style: none; -webkit-overflow-scrolling: touch;"
|
|
|
|
)
|
|
|
|
|
|
|
|
scrollbar_cls = "flex touch-none select-none transition-colors p-[1px]"
|
|
|
|
|
|
|
|
|
|
|
|
def ScrollArea(
|
|
|
|
*c: Any, cls: Optional[Union[str, tuple]] = None, orientation: str = "vertical", **kwargs: Any
|
|
|
|
) -> Div:
|
|
|
|
"""Creates a styled scroll area.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
orientation (str): The orientation of the scroll area. Defaults to vertical.
|
|
|
|
"""
|
|
|
|
new_cls = "relative overflow-hidden"
|
|
|
|
if cls:
|
|
|
|
new_cls += f" {stringify(cls)}"
|
|
|
|
kwargs["cls"] = new_cls
|
|
|
|
|
|
|
|
content = Div(
|
|
|
|
Div(*c, style="min-width:100%;display:table;"),
|
|
|
|
style=f"overflow: {'hidden scroll' if orientation == 'vertical' else 'scroll'}; {scrollbar_viewport_styles}",
|
|
|
|
cls="w-full h-full rounded-[inherit]",
|
|
|
|
data_ref="viewport",
|
|
|
|
)
|
|
|
|
|
|
|
|
scrollbar = Div(
|
|
|
|
Div(cls="bg-border rounded-full hidden relative flex-1", data_ref="thumb"),
|
|
|
|
cls=f"{scrollbar_cls} flex-col h-2.5 w-full border-t border-t-transparent"
|
|
|
|
if orientation == "horizontal"
|
|
|
|
else f"{scrollbar_cls} w-2.5 h-full border-l border-l-transparent",
|
|
|
|
data_ref="scrollbar",
|
|
|
|
style=f"position: absolute;{'right:0; top:0;' if orientation == 'vertical' else 'bottom:0; left:0;'}",
|
|
|
|
)
|
|
|
|
|
|
|
|
return Div(
|
|
|
|
content,
|
|
|
|
scrollbar,
|
|
|
|
role="region",
|
|
|
|
tabindex="0",
|
|
|
|
data_orientation=orientation,
|
|
|
|
data_ref_scrollarea=True,
|
|
|
|
aria_label="Scrollable content",
|
|
|
|
**kwargs,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2025-04-05 13:08:12 +02:00
|
|
|
def Success(*c: Any) -> Alert:
|
|
|
|
return Alert(
|
|
|
|
DivLAligned(
|
|
|
|
UkIcon("check"),
|
|
|
|
P(*c),
|
|
|
|
),
|
|
|
|
cls=AlertT.success,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def Error(*c: Any) -> Alert:
|
|
|
|
return Alert(
|
|
|
|
DivLAligned(
|
|
|
|
UkIcon("triangle-alert"),
|
|
|
|
P(*c),
|
|
|
|
),
|
|
|
|
cls=AlertT.error,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2025-01-22 23:47:28 +01:00
|
|
|
def ConfigCard(
|
2025-03-27 21:53:01 +01:00
|
|
|
config_name: str,
|
|
|
|
config_type: str,
|
|
|
|
read_only: str,
|
|
|
|
value: str,
|
|
|
|
default: str,
|
|
|
|
description: str,
|
2025-06-10 22:00:28 +02:00
|
|
|
deprecated: Optional[Union[str, bool]],
|
2025-03-27 21:53:01 +01:00
|
|
|
update_error: Optional[str],
|
|
|
|
update_value: Optional[str],
|
|
|
|
update_open: Optional[bool],
|
2025-01-22 23:47:28 +01:00
|
|
|
) -> Card:
|
2025-04-05 13:08:12 +02:00
|
|
|
"""Creates a styled configuration card for displaying configuration details.
|
|
|
|
|
|
|
|
This function generates a configuration card that is displayed in the UI with
|
|
|
|
various sections such as configuration name, type, description, default value,
|
|
|
|
current value, and error details. It supports both read-only and editable modes.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
config_name (str): The name of the configuration.
|
|
|
|
config_type (str): The type of the configuration.
|
|
|
|
read_only (str): Indicates if the configuration is read-only ("rw" for read-write,
|
|
|
|
any other value indicates read-only).
|
|
|
|
value (str): The current value of the configuration.
|
|
|
|
default (str): The default value of the configuration.
|
|
|
|
description (str): A description of the configuration.
|
2025-06-10 22:00:28 +02:00
|
|
|
deprecated (Optional[Union[str, bool]]): The deprecated marker of the configuration.
|
2025-04-05 13:08:12 +02:00
|
|
|
update_error (Optional[str]): The error message, if any, during the update process.
|
|
|
|
update_value (Optional[str]): The value to be updated, if different from the current value.
|
|
|
|
update_open (Optional[bool]): A flag indicating whether the update section of the card
|
|
|
|
should be initially expanded.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Card: A styled Card component containing the configuration details.
|
|
|
|
"""
|
2025-03-27 21:53:01 +01:00
|
|
|
config_id = config_name.replace(".", "-")
|
|
|
|
if not update_value:
|
|
|
|
update_value = value
|
|
|
|
if not update_open:
|
|
|
|
update_open = False
|
2025-06-10 22:00:28 +02:00
|
|
|
if deprecated:
|
|
|
|
if isinstance(deprecated, bool):
|
|
|
|
deprecated = "Deprecated"
|
2025-01-22 23:47:28 +01:00
|
|
|
return Card(
|
|
|
|
Details(
|
|
|
|
Summary(
|
|
|
|
Grid(
|
|
|
|
Grid(
|
|
|
|
DivLAligned(
|
|
|
|
UkIcon(icon="play"),
|
|
|
|
P(config_name),
|
|
|
|
),
|
|
|
|
DivRAligned(
|
|
|
|
P(read_only),
|
|
|
|
),
|
|
|
|
),
|
2025-03-27 21:53:01 +01:00
|
|
|
P(value),
|
2025-01-22 23:47:28 +01:00
|
|
|
),
|
|
|
|
cls="list-none",
|
|
|
|
),
|
|
|
|
Grid(
|
|
|
|
P(description),
|
|
|
|
P(config_type),
|
2025-06-10 22:00:28 +02:00
|
|
|
)
|
|
|
|
if not deprecated
|
|
|
|
else None,
|
|
|
|
Grid(
|
|
|
|
P(deprecated),
|
|
|
|
P("DEPRECATED!"),
|
|
|
|
)
|
|
|
|
if deprecated
|
|
|
|
else None,
|
2025-03-27 21:53:01 +01:00
|
|
|
# Default
|
|
|
|
Grid(
|
|
|
|
DivRAligned(P("default")),
|
|
|
|
P(default),
|
|
|
|
)
|
2025-06-10 22:00:28 +02:00
|
|
|
if read_only == "rw" and not deprecated
|
2025-03-27 21:53:01 +01:00
|
|
|
else None,
|
|
|
|
# Set value
|
2025-01-22 23:47:28 +01:00
|
|
|
Grid(
|
2025-03-27 21:53:01 +01:00
|
|
|
DivRAligned(P("update")),
|
|
|
|
Grid(
|
|
|
|
Form(
|
|
|
|
Input(value=config_name, type="hidden", id="key"),
|
|
|
|
Input(value=update_value, type="text", id="value"),
|
|
|
|
hx_put="/eosdash/configuration",
|
|
|
|
hx_target="#page-content",
|
|
|
|
hx_swap="innerHTML",
|
|
|
|
),
|
2025-01-22 23:47:28 +01:00
|
|
|
),
|
|
|
|
)
|
2025-06-10 22:00:28 +02:00
|
|
|
if read_only == "rw" and not deprecated
|
2025-01-22 23:47:28 +01:00
|
|
|
else None,
|
2025-03-27 21:53:01 +01:00
|
|
|
# Last error
|
|
|
|
Grid(
|
|
|
|
DivRAligned(P("update error")),
|
|
|
|
P(update_error),
|
|
|
|
)
|
|
|
|
if update_error
|
|
|
|
else None,
|
2025-01-22 23:47:28 +01:00
|
|
|
cls="space-y-4 gap-4",
|
2025-03-27 21:53:01 +01:00
|
|
|
open=update_open,
|
2025-01-22 23:47:28 +01:00
|
|
|
),
|
|
|
|
cls="w-full",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def DashboardHeader(title: Optional[str]) -> Div:
|
|
|
|
"""Creates a styled header with a title.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
title (Optional[str]): The title text for the header.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Div: A styled `Div` element containing the header.
|
|
|
|
"""
|
|
|
|
if title is None:
|
|
|
|
return Div("", cls="header")
|
|
|
|
return Div(H1(title, cls="text-2xl font-bold mb-4"), cls="header")
|
|
|
|
|
|
|
|
|
|
|
|
def DashboardFooter(*c: Any, path: str) -> Card:
|
|
|
|
"""Creates a styled footer with the provided information.
|
|
|
|
|
|
|
|
The footer content is reloaded every 5 seconds from path.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
path (str): Path to reload footer content from
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Card: A styled `Card` element containing the footer.
|
|
|
|
"""
|
|
|
|
return Card(
|
|
|
|
Container(*c, 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:
|
|
|
|
"""Creates a styled button for the dashboard trigger.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
*c: Positional arguments to pass to the button.
|
|
|
|
cls (Optional[str]): Additional CSS classes for styling. Defaults to None.
|
|
|
|
**kwargs: Additional keyword arguments for the button.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Button: A styled `Button` component.
|
|
|
|
"""
|
|
|
|
new_cls = f"{ButtonT.primary}"
|
|
|
|
if cls:
|
|
|
|
new_cls += f" {stringify(cls)}"
|
|
|
|
kwargs["cls"] = new_cls
|
|
|
|
return Button(*c, submit=False, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
def DashboardTabs(dashboard_items: dict[str, str]) -> Card:
|
|
|
|
"""Creates a dashboard tab with dynamic dashboard items.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
dashboard_items (dict[str, str]): A dictionary of dashboard items where keys are item names
|
|
|
|
and values are paths for navigation.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Card: A styled `Card` component containing the dashboard tabs.
|
|
|
|
"""
|
|
|
|
dash_items = [
|
|
|
|
Li(
|
|
|
|
DashboardTrigger(
|
2025-04-05 13:08:12 +02:00
|
|
|
H3(menu),
|
2025-01-22 23:47:28 +01:00
|
|
|
hx_get=f"{path}",
|
|
|
|
hx_target="#page-content",
|
|
|
|
hx_swap="innerHTML",
|
|
|
|
),
|
|
|
|
)
|
|
|
|
for menu, path in dashboard_items.items()
|
|
|
|
]
|
|
|
|
return Card(TabContainer(*dash_items, cls="gap-4"), alt=True)
|
|
|
|
|
|
|
|
|
|
|
|
def DashboardContent(content: Any) -> Card:
|
|
|
|
"""Creates a content section within a styled card.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
content (Any): The content to display.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Card: A styled `Card` element containing the 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_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_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(
|
|
|
|
DashboardHeader(title),
|
|
|
|
DashboardTabs(dashboard_items),
|
|
|
|
DashboardContent(content),
|
|
|
|
DashboardFooter(footer_content, path=footer_path),
|
|
|
|
cls=("bg-background text-foreground w-screen p-4 space-y-4", ContainerT.xl),
|
|
|
|
)
|