Added Layout and UserProfile
This commit is contained in:
18
src/app.py
18
src/app.py
@@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from fasthtml import serve
|
from fasthtml import serve
|
||||||
|
from fasthtml.components import *
|
||||||
|
|
||||||
from myfasthtml.controls.Layout import Layout
|
from myfasthtml.controls.Layout import Layout
|
||||||
from myfasthtml.myfastapp import create_app
|
from myfasthtml.myfastapp import create_app
|
||||||
@@ -11,13 +12,26 @@ logging.basicConfig(
|
|||||||
datefmt='%Y-%m-%d %H:%M:%S', # Timestamp format
|
datefmt='%Y-%m-%d %H:%M:%S', # Timestamp format
|
||||||
)
|
)
|
||||||
|
|
||||||
app, rt = create_app(protect_routes=False, mount_auth_app=True, pico=False, title="MyFastHtml" )
|
app, rt = create_app(protect_routes=True,
|
||||||
|
mount_auth_app=True,
|
||||||
|
pico=False,
|
||||||
|
title="MyFastHtml",
|
||||||
|
live=True,
|
||||||
|
base_url="http://localhost:5003")
|
||||||
|
|
||||||
|
|
||||||
@rt("/")
|
@rt("/")
|
||||||
def index(session):
|
def index(session):
|
||||||
layout = Layout(session, "Testing Layout", right_drawer=False)
|
layout = Layout(session, "Testing Layout")
|
||||||
layout.set_footer("Goodbye World")
|
layout.set_footer("Goodbye World")
|
||||||
|
for i in range(1000):
|
||||||
|
layout.left_drawer.append(Div(f"Left Drawer Item {i}"))
|
||||||
|
|
||||||
|
content = tuple([Div(f"Content {i}") for i in range(1000)])
|
||||||
|
layout.set_main(content)
|
||||||
return layout
|
return layout
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
# debug_routes(app)
|
||||||
serve(port=5003)
|
serve(port=5003)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
grid-area: header;
|
grid-area: header;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between; /* put one item on each side */
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
background-color: var(--color-base-300);
|
background-color: var(--color-base-300);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ def setup_auth_routes(app, rt, mount_auth_app=True, sqlite_db_path="Users.db", b
|
|||||||
session['refresh_token'] = auth_data['refresh_token']
|
session['refresh_token'] = auth_data['refresh_token']
|
||||||
|
|
||||||
# Get user info and store in session
|
# Get user info and store in session
|
||||||
user_info = get_user_info(auth_data['access_token'])
|
user_info = get_user_info(auth_data['access_token'], base_url=base_url)
|
||||||
if user_info:
|
if user_info:
|
||||||
session['user_info'] = user_info
|
session['user_info'] = user_info
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ def setup_auth_routes(app, rt, mount_auth_app=True, sqlite_db_path="Users.db", b
|
|||||||
return RegisterPage(error_message="Password must be at least 8 characters long.")
|
return RegisterPage(error_message="Password must be at least 8 characters long.")
|
||||||
|
|
||||||
# Attempt registration
|
# Attempt registration
|
||||||
result = register_user(email, username, password)
|
result = register_user(email, username, password, base_url=base_url)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
# Registration successful - show success message and auto-login
|
# Registration successful - show success message and auto-login
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
class BaseControl:
|
|
||||||
def __init__(self, session, _id):
|
|
||||||
self.session = session
|
|
||||||
self._id = _id
|
|
||||||
|
|
||||||
def get_id(self):
|
|
||||||
return self._id
|
|
||||||
@@ -5,15 +5,15 @@ This component provides a responsive layout with fixed header/footer,
|
|||||||
optional collapsible left/right drawers, and a scrollable main content area.
|
optional collapsible left/right drawers, and a scrollable main content area.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from fasthtml.common import *
|
from fasthtml.common import *
|
||||||
|
|
||||||
from myfasthtml.controls.BaseCommands import BaseCommands
|
from myfasthtml.controls.BaseCommands import BaseCommands
|
||||||
from myfasthtml.controls.BaseControl import BaseControl
|
from myfasthtml.controls.UserProfile import UserProfile
|
||||||
from myfasthtml.controls.helpers import mk
|
from myfasthtml.controls.helpers import mk, Ids
|
||||||
from myfasthtml.core.commands import Command
|
from myfasthtml.core.commands import Command
|
||||||
|
from myfasthtml.core.instances import MultipleInstance, InstancesManager
|
||||||
from myfasthtml.icons.fluent import panel_left_expand20_regular as left_drawer_icon
|
from myfasthtml.icons.fluent import panel_left_expand20_regular as left_drawer_icon
|
||||||
from myfasthtml.icons.fluent_p2 import panel_right_expand20_regular as right_drawer_icon
|
from myfasthtml.icons.fluent_p2 import panel_right_expand20_regular as right_drawer_icon
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ logger = logging.getLogger("LayoutControl")
|
|||||||
@dataclass
|
@dataclass
|
||||||
class LayoutState:
|
class LayoutState:
|
||||||
left_drawer_open: bool = True
|
left_drawer_open: bool = True
|
||||||
|
right_drawer_open: bool = False
|
||||||
|
|
||||||
|
|
||||||
class Commands(BaseCommands):
|
class Commands(BaseCommands):
|
||||||
@@ -30,7 +31,7 @@ class Commands(BaseCommands):
|
|||||||
return Command("ToggleDrawer", "Toggle main layout drawer", self._owner.toggle_drawer, "left")
|
return Command("ToggleDrawer", "Toggle main layout drawer", self._owner.toggle_drawer, "left")
|
||||||
|
|
||||||
|
|
||||||
class Layout(BaseControl):
|
class Layout(MultipleInstance):
|
||||||
"""
|
"""
|
||||||
A responsive layout component with header, footer, main content area,
|
A responsive layout component with header, footer, main content area,
|
||||||
and optional collapsible side drawers.
|
and optional collapsible side drawers.
|
||||||
@@ -41,7 +42,19 @@ class Layout(BaseControl):
|
|||||||
right_drawer (bool): Whether to include a right drawer
|
right_drawer (bool): Whether to include a right drawer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session, app_name, left_drawer=True, right_drawer=True):
|
class DrawerContent:
|
||||||
|
def __init__(self, owner, side: Literal["left", "right"]):
|
||||||
|
self._owner = owner
|
||||||
|
self.side = side
|
||||||
|
self._content = []
|
||||||
|
|
||||||
|
def append(self, content):
|
||||||
|
self._content.append(content)
|
||||||
|
|
||||||
|
def get_content(self):
|
||||||
|
return self._content
|
||||||
|
|
||||||
|
def __init__(self, session, app_name):
|
||||||
"""
|
"""
|
||||||
Initialize the Layout component.
|
Initialize the Layout component.
|
||||||
|
|
||||||
@@ -50,28 +63,17 @@ class Layout(BaseControl):
|
|||||||
left_drawer (bool): Enable left drawer. Default is True.
|
left_drawer (bool): Enable left drawer. Default is True.
|
||||||
right_drawer (bool): Enable right drawer. Default is True.
|
right_drawer (bool): Enable right drawer. Default is True.
|
||||||
"""
|
"""
|
||||||
super().__init__(session, f"mf-layout-{str(uuid.uuid4())}")
|
super().__init__(session, Ids.Layout)
|
||||||
self.app_name = app_name
|
self.app_name = app_name
|
||||||
self.left_drawer = left_drawer
|
|
||||||
self.right_drawer = right_drawer
|
|
||||||
|
|
||||||
# Content storage
|
# Content storage
|
||||||
self._header_content = None
|
self._header_content = None
|
||||||
self._footer_content = None
|
self._footer_content = None
|
||||||
self._main_content = None
|
self._main_content = None
|
||||||
self._left_drawer_content = None
|
|
||||||
self._right_drawer_content = None
|
|
||||||
self._state = LayoutState()
|
self._state = LayoutState()
|
||||||
self.commands = Commands(self)
|
self.commands = Commands(self)
|
||||||
|
self.left_drawer = self.DrawerContent(self, "left")
|
||||||
# def set_header(self, content):
|
self.right_drawer = self.DrawerContent(self, "right")
|
||||||
# """
|
|
||||||
# Set the header content.
|
|
||||||
#
|
|
||||||
# Args:
|
|
||||||
# content: FastHTML component(s) or content for the header
|
|
||||||
# """
|
|
||||||
# self._header_content = content
|
|
||||||
|
|
||||||
def set_footer(self, content):
|
def set_footer(self, content):
|
||||||
"""
|
"""
|
||||||
@@ -91,26 +93,6 @@ class Layout(BaseControl):
|
|||||||
"""
|
"""
|
||||||
self._main_content = content
|
self._main_content = content
|
||||||
|
|
||||||
def set_left_drawer(self, content):
|
|
||||||
"""
|
|
||||||
Set the left drawer content.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
content: FastHTML component(s) or content for the left drawer
|
|
||||||
"""
|
|
||||||
if self.left_drawer:
|
|
||||||
self._left_drawer_content = content
|
|
||||||
|
|
||||||
def set_right_drawer(self, content):
|
|
||||||
"""
|
|
||||||
Set the right drawer content.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
content: FastHTML component(s) or content for the right drawer
|
|
||||||
"""
|
|
||||||
if self.right_drawer:
|
|
||||||
self._right_drawer_content = content
|
|
||||||
|
|
||||||
def toggle_drawer(self, side: Literal["left", "right"]):
|
def toggle_drawer(self, side: Literal["left", "right"]):
|
||||||
logger.debug(f"Toggle drawer: {side=}, {self._state.left_drawer_open=}")
|
logger.debug(f"Toggle drawer: {side=}, {self._state.left_drawer_open=}")
|
||||||
if side == "left":
|
if side == "left":
|
||||||
@@ -118,7 +100,7 @@ class Layout(BaseControl):
|
|||||||
return self._mk_left_drawer_icon(), self._mk_left_drawer()
|
return self._mk_left_drawer_icon(), self._mk_left_drawer()
|
||||||
elif side == "right":
|
elif side == "right":
|
||||||
self._state.right_drawer_open = not self._state.right_drawer_open
|
self._state.right_drawer_open = not self._state.right_drawer_open
|
||||||
return Div(), self._mk_right_drawer()
|
return self._mk_left_drawer_icon(), self._mk_right_drawer()
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid drawer side")
|
raise ValueError("Invalid drawer side")
|
||||||
|
|
||||||
@@ -131,6 +113,7 @@ class Layout(BaseControl):
|
|||||||
"""
|
"""
|
||||||
return Header(
|
return Header(
|
||||||
self._mk_left_drawer_icon(),
|
self._mk_left_drawer_icon(),
|
||||||
|
InstancesManager.get(self._session, Ids.UserProfile, UserProfile),
|
||||||
cls="mf-layout-header"
|
cls="mf-layout-header"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -167,17 +150,10 @@ class Layout(BaseControl):
|
|||||||
Returns:
|
Returns:
|
||||||
Div or None: FastHTML Div component for left drawer, or None if disabled
|
Div or None: FastHTML Div component for left drawer, or None if disabled
|
||||||
"""
|
"""
|
||||||
if not self.left_drawer:
|
|
||||||
return None
|
|
||||||
|
|
||||||
print(f"{self._state.left_drawer_open=}")
|
|
||||||
|
|
||||||
drawer_content = self._left_drawer_content if self._left_drawer_content else ""
|
|
||||||
return Div(
|
return Div(
|
||||||
drawer_content,
|
*self.left_drawer.get_content(),
|
||||||
id=f"{self._id}_ld",
|
id=f"{self._id}_ld",
|
||||||
cls=f"mf-layout-drawer mf-layout-left-drawer {'collapsed' if not self._state.left_drawer_open else ''}",
|
cls=f"mf-layout-drawer mf-layout-left-drawer {'collapsed' if not self._state.left_drawer_open else ''}",
|
||||||
**{"data-side": "left"}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _mk_right_drawer(self):
|
def _mk_right_drawer(self):
|
||||||
@@ -187,15 +163,10 @@ class Layout(BaseControl):
|
|||||||
Returns:
|
Returns:
|
||||||
Div or None: FastHTML Div component for right drawer, or None if disabled
|
Div or None: FastHTML Div component for right drawer, or None if disabled
|
||||||
"""
|
"""
|
||||||
if not self.right_drawer:
|
|
||||||
return None
|
|
||||||
|
|
||||||
drawer_content = self._right_drawer_content if self._right_drawer_content else ""
|
|
||||||
return Div(
|
return Div(
|
||||||
drawer_content,
|
*self.right_drawer.get_content(),
|
||||||
cls="mf-layout-drawer mf-layout-right-drawer",
|
cls=f"mf-layout-drawer mf-layout-right-drawer {'collapsed' if not self._state.right_drawer_open else ''}",
|
||||||
id=f"{self._id}_rd",
|
id=f"{self._id}_rd",
|
||||||
**{"data-side": "right"}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _mk_left_drawer_icon(self):
|
def _mk_left_drawer_icon(self):
|
||||||
@@ -218,13 +189,8 @@ class Layout(BaseControl):
|
|||||||
self._mk_main(),
|
self._mk_main(),
|
||||||
self._mk_right_drawer(),
|
self._mk_right_drawer(),
|
||||||
self._mk_footer(),
|
self._mk_footer(),
|
||||||
Script(f"initLayout('{self._id}');"),
|
|
||||||
id=self._id,
|
id=self._id,
|
||||||
cls="mf-layout",
|
cls="mf-layout",
|
||||||
**{
|
|
||||||
"data-left-drawer": str(self.left_drawer).lower(),
|
|
||||||
"data-right-drawer": str(self.right_drawer).lower()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __ft__(self):
|
def __ft__(self):
|
||||||
|
|||||||
46
src/myfasthtml/controls/UserProfile.py
Normal file
46
src/myfasthtml/controls/UserProfile.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from fasthtml.components import *
|
||||||
|
|
||||||
|
from myfasthtml.controls.helpers import Ids, mk
|
||||||
|
from myfasthtml.core.instances import SingleInstance
|
||||||
|
from myfasthtml.core.utils import get_user_info
|
||||||
|
from myfasthtml.icons.material import dark_mode_filled, person_outline_sharp
|
||||||
|
from myfasthtml.icons.material_p1 import light_mode_filled, alternate_email_filled
|
||||||
|
|
||||||
|
|
||||||
|
class UserProfile(SingleInstance):
|
||||||
|
def __init__(self, session):
|
||||||
|
super().__init__(session, Ids.UserProfile)
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
user_info = get_user_info(self._session)
|
||||||
|
return Div(
|
||||||
|
Div(user_info['username'],
|
||||||
|
tabindex="0",
|
||||||
|
role="button",
|
||||||
|
cls="btn btn-xs"),
|
||||||
|
Div(
|
||||||
|
Div(mk.icon(person_outline_sharp, cls="mr-1"), user_info['username'], cls="flex m-1"),
|
||||||
|
Div(mk.icon(alternate_email_filled, cls="mr-1"), user_info['email'], cls="flex m-1"),
|
||||||
|
Div(mk.icon(dark_mode_filled, cls="mr-1"), self.mk_dark_mode(), cls="flex m-1"),
|
||||||
|
Div(A("Logout", cls="btn btn-xs mr-1", href="/logout"), cls="flex justify-center items-center"),
|
||||||
|
tabindex="-1",
|
||||||
|
cls="dropdown-content menu w-52 rounded-box bg-base-300 shadow-xl"
|
||||||
|
),
|
||||||
|
cls="dropdown dropdown-end"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def mk_dark_mode():
|
||||||
|
return Label(
|
||||||
|
Input(type="checkbox",
|
||||||
|
name='theme',
|
||||||
|
aria_label='Dark',
|
||||||
|
value='dark',
|
||||||
|
cls='theme-controller'),
|
||||||
|
light_mode_filled,
|
||||||
|
dark_mode_filled,
|
||||||
|
cls="toggle text-base-content"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __ft__(self):
|
||||||
|
return self.render()
|
||||||
@@ -3,7 +3,9 @@ from fasthtml.components import *
|
|||||||
from myfasthtml.core.bindings import Binding
|
from myfasthtml.core.bindings import Binding
|
||||||
from myfasthtml.core.commands import Command
|
from myfasthtml.core.commands import Command
|
||||||
from myfasthtml.core.utils import merge_classes
|
from myfasthtml.core.utils import merge_classes
|
||||||
|
class Ids:
|
||||||
|
Layout = "mf-layout"
|
||||||
|
UserProfile = "mf-user-profile"
|
||||||
|
|
||||||
class mk:
|
class mk:
|
||||||
|
|
||||||
|
|||||||
@@ -270,10 +270,10 @@ class Binding:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def get_htmx_params(self):
|
def get_htmx_params(self):
|
||||||
return self.htmx_extra | {
|
return {
|
||||||
"hx-post": f"{ROUTE_ROOT}{Routes.Bindings}",
|
"hx-post": f"{ROUTE_ROOT}{Routes.Bindings}",
|
||||||
"hx-vals": f'{{"b_id": "{self.id}"}}',
|
"hx-vals": f'{{"b_id": "{self.id}"}}',
|
||||||
}
|
} | self.htmx_extra
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ class BaseCommand:
|
|||||||
CommandsManager.register(self)
|
CommandsManager.register(self)
|
||||||
|
|
||||||
def get_htmx_params(self):
|
def get_htmx_params(self):
|
||||||
return self._htmx_extra | {
|
return {
|
||||||
"hx-post": f"{ROUTE_ROOT}{Routes.Commands}",
|
"hx-post": f"{ROUTE_ROOT}{Routes.Commands}",
|
||||||
"hx-swap": "outerHTML",
|
"hx-swap": "outerHTML",
|
||||||
"hx-vals": f'{{"c_id": "{self.id}"}}',
|
"hx-vals": f'{{"c_id": "{self.id}"}}',
|
||||||
}
|
} | self._htmx_extra
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|||||||
89
src/myfasthtml/core/instances.py
Normal file
89
src/myfasthtml/core/instances.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class DuplicateInstanceError(Exception):
|
||||||
|
def __init__(self, instance):
|
||||||
|
self.instance = instance
|
||||||
|
|
||||||
|
|
||||||
|
class BaseInstance:
|
||||||
|
"""
|
||||||
|
Base class for all instances (manageable by InstancesManager)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, session: dict, _id: str):
|
||||||
|
self._session = session
|
||||||
|
self._id = _id
|
||||||
|
InstancesManager.register(session, self)
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
def get_session(self):
|
||||||
|
return self._session
|
||||||
|
|
||||||
|
|
||||||
|
class SingleInstance(BaseInstance):
|
||||||
|
"""
|
||||||
|
Base class for instances that can only have one instance at a time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, session: dict, prefix: str):
|
||||||
|
super().__init__(session, prefix)
|
||||||
|
self._instance = None
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleInstance(BaseInstance):
|
||||||
|
"""
|
||||||
|
Base class for instances that can have multiple instances at a time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, session: dict, prefix: str):
|
||||||
|
super().__init__(session, f"{prefix}-{str(uuid.uuid4())}")
|
||||||
|
self._instance = None
|
||||||
|
|
||||||
|
|
||||||
|
class InstancesManager:
|
||||||
|
instances = {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def register(session: dict, instance: BaseInstance):
|
||||||
|
"""
|
||||||
|
Register an instance in the manager, so that it can be retrieved later.
|
||||||
|
:param session:
|
||||||
|
:param instance:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
key = (InstancesManager._get_session_id(session), instance.get_id())
|
||||||
|
|
||||||
|
if isinstance(instance, SingleInstance) and key in InstancesManager.instances:
|
||||||
|
raise DuplicateInstanceError(instance)
|
||||||
|
|
||||||
|
InstancesManager.instances[key] = instance
|
||||||
|
return instance
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get(session: dict, instance_id: str, instance_type: type = None, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Get or create an instance of the given type (from its id)
|
||||||
|
:param session:
|
||||||
|
:param instance_id:
|
||||||
|
:param instance_type:
|
||||||
|
:param args:
|
||||||
|
:param kwargs:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
key = (InstancesManager._get_session_id(session), instance_id)
|
||||||
|
|
||||||
|
return InstancesManager.instances[key]
|
||||||
|
except KeyError:
|
||||||
|
return instance_type(session, *args, **kwargs) # it will be automatically registered
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_session_id(session):
|
||||||
|
if not session:
|
||||||
|
return "** NOT LOGGED IN **"
|
||||||
|
if "user_info" not in session:
|
||||||
|
return "** UNKNOWN USER **"
|
||||||
|
return session["user_info"].get("id", "** INVALID SESSION **")
|
||||||
@@ -3,6 +3,8 @@ import logging
|
|||||||
from bs4 import Tag
|
from bs4 import Tag
|
||||||
from fastcore.xml import FT
|
from fastcore.xml import FT
|
||||||
from fasthtml.fastapp import fast_app
|
from fasthtml.fastapp import fast_app
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
from starlette.routing import Mount
|
from starlette.routing import Mount
|
||||||
|
|
||||||
from myfasthtml.core.constants import Routes, ROUTE_ROOT
|
from myfasthtml.core.constants import Routes, ROUTE_ROOT
|
||||||
@@ -60,16 +62,47 @@ def merge_classes(*args):
|
|||||||
|
|
||||||
|
|
||||||
def debug_routes(app):
|
def debug_routes(app):
|
||||||
|
routes = []
|
||||||
|
|
||||||
|
def _clean_endpoint(endpoint):
|
||||||
|
res = str(endpoint).replace("<function ", "").replace(".<locals>", "")
|
||||||
|
return res.split(" at ")[0]
|
||||||
|
|
||||||
def _debug_routes(_app, _route, prefix=""):
|
def _debug_routes(_app, _route, prefix=""):
|
||||||
if isinstance(_route, Mount):
|
if isinstance(_route, Mount):
|
||||||
for sub_route in _route.app.router.routes:
|
for sub_route in _route.app.router.routes:
|
||||||
_debug_routes(_app, sub_route, prefix=_route.path)
|
_debug_routes(_app, sub_route, prefix=_route.path)
|
||||||
else:
|
else:
|
||||||
print(f"path={prefix}{_route.path}, methods={_route.methods}, endpoint={_route.endpoint}")
|
routes.append({
|
||||||
|
"number": len(routes),
|
||||||
|
"app": str(_app),
|
||||||
|
"name": _route.name,
|
||||||
|
"path": _route.path,
|
||||||
|
"full_path": prefix + _route.path,
|
||||||
|
"endpoint": _clean_endpoint(_route.endpoint),
|
||||||
|
"methods": _route.methods if hasattr(_route, "methods") else [],
|
||||||
|
"path_format": _route.path_format,
|
||||||
|
"path_regex": str(_route.path_regex),
|
||||||
|
})
|
||||||
|
|
||||||
for route in app.router.routes:
|
for route in app.router.routes:
|
||||||
_debug_routes(app, route)
|
_debug_routes(app, route)
|
||||||
|
|
||||||
|
if not routes:
|
||||||
|
print("No routes found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
table = Table(show_header=True, expand=True, header_style="bold")
|
||||||
|
columns = ["number", "name", "full_path", "endpoint", "methods"] # routes[0].keys()
|
||||||
|
for column in columns:
|
||||||
|
table.add_column(column)
|
||||||
|
|
||||||
|
for route in routes:
|
||||||
|
table.add_row(*[str(route[column]) for column in columns])
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
def mount_utils(app):
|
def mount_utils(app):
|
||||||
"""
|
"""
|
||||||
@@ -158,6 +191,26 @@ def quoted_str(s):
|
|||||||
return str(s)
|
return str(s)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_info(session: dict):
|
||||||
|
if not session:
|
||||||
|
return {
|
||||||
|
"id": "** NOT LOGGED IN **",
|
||||||
|
"email": "** NOT LOGGED IN **",
|
||||||
|
"username": "** NOT LOGGED IN **",
|
||||||
|
"role": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
if "user_info" not in session:
|
||||||
|
return {
|
||||||
|
"id": "** UNKNOWN USER **",
|
||||||
|
"email": "** UNKNOWN USER **",
|
||||||
|
"username": "** UNKNOWN USER **",
|
||||||
|
"role": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
return session["user_info"]
|
||||||
|
|
||||||
|
|
||||||
@utils_rt(Routes.Commands)
|
@utils_rt(Routes.Commands)
|
||||||
def post(session, c_id: str):
|
def post(session, c_id: str):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ def get_asset_content(filename):
|
|||||||
def create_app(daisyui: Optional[bool] = True,
|
def create_app(daisyui: Optional[bool] = True,
|
||||||
protect_routes: Optional[bool] = True,
|
protect_routes: Optional[bool] = True,
|
||||||
mount_auth_app: Optional[bool] = False,
|
mount_auth_app: Optional[bool] = False,
|
||||||
|
base_url: Optional[str] = None,
|
||||||
**kwargs) -> Any:
|
**kwargs) -> Any:
|
||||||
"""
|
"""
|
||||||
Creates and configures a FastHtml application with optional support for daisyUI themes and
|
Creates and configures a FastHtml application with optional support for daisyUI themes and
|
||||||
@@ -39,16 +40,11 @@ def create_app(daisyui: Optional[bool] = True,
|
|||||||
|
|
||||||
:param daisyui: Flag to enable or disable inclusion of daisyUI-related assets for styling.
|
:param daisyui: Flag to enable or disable inclusion of daisyUI-related assets for styling.
|
||||||
Defaults to False.
|
Defaults to False.
|
||||||
:type daisyui: Optional[bool]
|
|
||||||
|
|
||||||
:param protect_routes: Flag to enable or disable routes protection based on authentication.
|
:param protect_routes: Flag to enable or disable routes protection based on authentication.
|
||||||
Defaults to True.
|
Defaults to True.
|
||||||
:type protect_routes: Optional[bool]
|
|
||||||
|
|
||||||
:param mount_auth_app: Flag to enable or disable mounting of authentication routes.
|
:param mount_auth_app: Flag to enable or disable mounting of authentication routes.
|
||||||
Defaults to False.
|
Defaults to False.
|
||||||
:type mount_auth_app: Optional[bool]
|
:param base_url: Url to use for the application (used by the auth APIs)
|
||||||
|
|
||||||
:param kwargs: Arbitrary keyword arguments forwarded to the application initialization logic.
|
:param kwargs: Arbitrary keyword arguments forwarded to the application initialization logic.
|
||||||
|
|
||||||
:return: A tuple containing the FastHtml application instance and the associated router.
|
:return: A tuple containing the FastHtml application instance and the associated router.
|
||||||
@@ -70,11 +66,12 @@ def create_app(daisyui: Optional[bool] = True,
|
|||||||
app, rt = fasthtml.fastapp.fast_app(before=beforeware, hdrs=tuple(hdrs), **kwargs)
|
app, rt = fasthtml.fastapp.fast_app(before=beforeware, hdrs=tuple(hdrs), **kwargs)
|
||||||
|
|
||||||
# remove the global static files routes
|
# remove the global static files routes
|
||||||
static_route_exts_get = app.routes.pop(0)
|
original_routes = app.routes[:]
|
||||||
|
app.routes.clear()
|
||||||
|
|
||||||
# Serve assets
|
# Serve assets
|
||||||
@app.get("/myfasthtml/{filename:path}.{ext:static}")
|
@app.get("/myfasthtml/{filename:path}.{ext:static}")
|
||||||
def serve_asset(filename: str, ext: str):
|
def serve_assets(filename: str, ext: str):
|
||||||
path = filename + "." + ext
|
path = filename + "." + ext
|
||||||
try:
|
try:
|
||||||
content = get_asset_content(path)
|
content = get_asset_content(path)
|
||||||
@@ -89,13 +86,14 @@ def create_app(daisyui: Optional[bool] = True,
|
|||||||
return Response(f"Asset not found: {path}", status_code=404)
|
return Response(f"Asset not found: {path}", status_code=404)
|
||||||
|
|
||||||
# and put it back after the myfasthtml static files routes
|
# and put it back after the myfasthtml static files routes
|
||||||
app.routes.append(static_route_exts_get)
|
for r in original_routes:
|
||||||
|
app.routes.append(r)
|
||||||
|
|
||||||
# route the commands and the bindings
|
# route the commands and the bindings
|
||||||
app.mount("/myfasthtml", utils_app)
|
app.mount("/myfasthtml", utils_app)
|
||||||
|
|
||||||
if mount_auth_app:
|
if mount_auth_app:
|
||||||
# Setup authentication routes
|
# Setup authentication routes
|
||||||
setup_auth_routes(app, rt)
|
setup_auth_routes(app, rt, base_url=base_url)
|
||||||
|
|
||||||
return app, rt
|
return app, rt
|
||||||
|
|||||||
Reference in New Issue
Block a user