Files
MyManagingTools/src/main.py
2025-07-01 22:07:12 +02:00

302 lines
11 KiB
Python

# global layout
import asyncio
import logging.config
import yaml
from fasthtml.common import *
from auth.auth_manager import AuthManager
from components.DrawerLayoutOld import DrawerLayout as DrawerLayoutOld
from components.DrawerLayoutOld import Page
from components.datagrid.DataGrid import DATAGRID_PATH, datagrid_app
from components.drawerlayout.components import DrawerLayout
from components.drawerlayout.components.DrawerLayout import DrawerLayout
from components.login.components.Login import Login
from components.login.constants import ROUTE_ROOT as LOGIN_ROUTE_ROOT
from components.login.constants import Routes as LoginRoutes
from components.page_layout_new import page_layout_new, page_layout_lite
from components.register.components.Register import Register
from components.register.constants import ROUTE_ROOT as REGISTER_ROUTE_ROOT
from components.register.constants import Routes as RegisterRoutes
from config import APP_PORT
from constants import Routes
from core.dbengine import DbException
from core.instance_manager import InstanceManager
from core.settings_management import SettingsManager
from pages.admin_import_settings import AdminImportSettings, IMPORT_SETTINGS_PATH, import_settings_app
from pages.another_grid import get_datagrid2
from pages.basic_test import BASIC_TEST_PATH, basic_test_app, get_basic_test
from pages.testing_datagrid import get_datagrid
from pages.testing_restore_state import testing_restore_state
# logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
# Load the YAML logging configuration
with open('logging.yaml', 'r') as f:
config = yaml.safe_load(f)
# At the top of your script or module
logging.config.dictConfig(config)
logger = logging.getLogger("MainApp")
# daisy_ui_links_v4 = (
# Link(href="https://cdn.jsdelivr.net/npm/daisyui@latest/dist/full.min.css", rel="stylesheet", type="text/css"),
# Script(src="https://cdn.tailwindcss.com"),
# )
links = [
# start with daisyui
# Link(href="https://cdn.jsdelivr.net/npm/daisyui@5", rel="stylesheet", type="text/css"),
# Link(href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css", rel="stylesheet", type="text/css"),
# Script(src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"),
Link(href="./assets/daisyui-5.css", rel="stylesheet", type="text/css"),
Link(href="./assets/daisyui-5-themes.css", rel="stylesheet", type="text/css"),
Script(src="./assets/tailwindcss-browser@4.js"),
# Old drawer layout
Script(src="./assets/DrawerLayout.js", defer=True),
Link(rel="stylesheet", href="./assets/DrawerLayout.css"),
# Old datagrid
Script(src="./components/datagrid/DataGrid.js"),
Link(rel="stylesheet", href="./components/datagrid/DataGrid.css"),
# add main
Script(src="./assets/main.js"),
Link(rel="stylesheet", href="./assets/main.css", type="text/css")
]
routes = []
def register_component(name, path, app_module_name):
def _get_fast_app(_module):
if app_module_name is None:
logger.debug(f" No app module defined for {_module.__name__}. Skipping fast app lookup.")
return None
for var_name in dir(_module):
if var_name.endswith('App'):
component_module = getattr(_module, var_name)
logger.debug(f" Found component module {component_module.__name__}")
for sub_var_name in dir(component_module):
if sub_var_name.endswith("_app"):
res = getattr(component_module, sub_var_name)
if isinstance(res, FastHTML):
logger.debug(f" Found FastHTML app component {sub_var_name}")
return res
raise ValueError(f" Cannot find app for component {name}")
def _get_route_root(_module):
if app_module_name is None:
logger.debug(f" No app module defined for {_module.__name__}. Skipping route root lookup.")
return None
constants_module = getattr(_module, "constants")
return getattr(constants_module, "ROUTE_ROOT")
def _get_links(_module):
# Get module file path and construct assets directory path
module_path = os.path.dirname(_module.__file__)
assets_path = os.path.join(module_path, 'assets')
if not os.path.exists(assets_path):
logger.debug(f" No 'assets' folder not found in module {_module.__name__}")
return None
# Find all CSS files in assets directory
files = []
for file in os.listdir(assets_path):
if file.endswith('.css'):
file_path = f'./{os.path.relpath(os.path.join(assets_path, file))}'
files.append(Link(rel="stylesheet", href=file_path, type="text/css"))
logger.debug(f" Found CSS file {file_path}")
elif file.endswith('.js'):
file_path = f'./{os.path.relpath(os.path.join(assets_path, file))}'
files.append(Script(src=file_path))
logger.debug(f" Found JS file {file_path}")
return files if files else None
try:
# Import the module dynamically
logger.debug(f"register_component {name=} {path=} {app_module_name=}")
module = __import__(path, fromlist=['*', app_module_name] if app_module_name else ['*'])
component_app = _get_fast_app(module)
component_route_root = _get_route_root(module)
component_links = _get_links(module)
if component_app is not None:
routes.append(Mount(component_route_root, component_app, name=name))
if component_links is not None:
links.extend(component_links)
except ImportError:
logger.error(f"Could not import module {path}. Failed to register component {name}.")
except AttributeError as ex:
logger.error(f"Error: {ex}. Failed to register component {name}.")
register_component("login", "components.login", "LoginApp")
register_component("register", "components.register", "RegisterApp")
register_component("theme_controller", "components.themecontroller", "ThemeControllerApp")
register_component("main_layout", "components.drawerlayout", "DrawerLayoutApp")
register_component("tabs", "components.tabs", "TabsApp") # before repositories
register_component("applications", "components.applications", "ApplicationsApp")
register_component("repositories", "components.repositories", "RepositoriesApp")
register_component("workflows", "components.workflows", "WorkflowsApp")
register_component("add_stuff", "components.addstuff", None)
register_component("form", "components.form", "FormApp")
register_component("datagrid_new", "components.datagrid_new", "DataGridApp")
register_component("debugger", "components.debugger", "DebuggerApp")
register_component("ai_buddy", "components.aibuddy", "AIBuddyApp")
register_component("admin", "components.admin", "AdminApp")
routes.extend([
# old stuffs
Mount(f"/{BASIC_TEST_PATH}", basic_test_app, name="basic test"),
Mount(f"/{DATAGRID_PATH}", datagrid_app, name="datagrid"),
Mount(f"/{IMPORT_SETTINGS_PATH}", import_settings_app, name="import_settings"),
])
old_links = (
# daisy_ui_links_v4
Link(href="./assets/daisyui-4.12.10-full-min.css", rel="stylesheet", type="text/css"),
Script(src="./assets/tailwindcss.js"),
# datagridOld
Script(src="./components/datagrid/DataGrid.js"),
Link(rel="stylesheet", href="./components/datagrid/DataGrid.css"),
# drw_layout_old
Script(src="./assets/DrawerLayout.js", defer=True),
Link(rel="stylesheet", href="./assets/DrawerLayout.css")
)
# The `before` function is a *Beforeware* function. These are functions that run before a route handler is called.
def before(request, session):
# This sets the `auth` attribute in the request scope, and gets it from the session.
# The session is a Starlette session, which is a dict-like object which is cryptographically signed,
# so it can't be tampered with.
# The `auth` key in the scope is automatically provided to any handler which requests it, and can not
# be injected by the user using query params, cookies, etc, so it should be secure to use.
# auth = request.scope['auth'] = sess.session('auth', None)
if not AuthManager.is_authenticated(session):
return RedirectResponse(LOGIN_ROUTE_ROOT + LoginRoutes.Login, status_code=303)
# To create a Beforeware object, we pass the function itself, and optionally a list of regexes to skip.
bware = Beforeware(before, skip=[r'/favicon\.ico',
r'/static/.*',
r'.*\.css',
LOGIN_ROUTE_ROOT + LoginRoutes.Login,
LOGIN_ROUTE_ROOT + LoginRoutes.Logout,
LOGIN_ROUTE_ROOT + LoginRoutes.LoginByEmail,
REGISTER_ROUTE_ROOT + RegisterRoutes.Register,
REGISTER_ROUTE_ROOT + RegisterRoutes.RegisterByEmail, ])
app, rt = fast_app(
before=bware,
hdrs=tuple(links),
live=True,
routes=tuple(routes),
debug=True,
pico=False,
)
settings_manager = SettingsManager()
import_settings = AdminImportSettings(settings_manager, None)
pages = [
Page("My table", get_datagrid, id="my_table"),
Page("new settings", import_settings, id="import_settings"),
Page("Basic test", get_basic_test, id="basic_test"),
Page("Restore state", testing_restore_state, id="testing_states"),
Page("Another Table", get_datagrid2, id="another_table"),
]
login = Login(settings_manager)
register = Register(settings_manager)
InstanceManager.register_many(login, register)
@rt(Routes.Root)
def get(session):
try:
main = InstanceManager.get(session,
DrawerLayout.create_component_id(session),
DrawerLayout,
settings_manager=settings_manager)
return page_layout_lite(session, settings_manager, main)
except DbException:
return RedirectResponse(LOGIN_ROUTE_ROOT + LoginRoutes.Logout, status_code=303)
@rt(Routes.Logout)
def get(session):
AuthManager.logout_user(session)
return RedirectResponse('/', status_code=303)
@rt("/test")
def get(session):
return (Title("Another Project Management"),
old_links,
Input(type='checkbox', value='light', cls='toggle theme-controller'),
DrawerLayoutOld(pages),)
# Error Handling
@app.get("/{path:path}")
def not_found(path: str, session=None):
"""Handler for 404 Not Found errors."""
error_content = Div(
H1("404 - Page Not Found", cls="text-3xl font-bold text-gray-800 mb-4"),
P(f"Sorry, the page '/{path}' does not exist.", cls="mb-4"),
A("Return Home", href="/",
cls="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"),
cls="max-w-2xl mx-auto bg-white p-6 rounded-lg shadow-md text-center"
)
return page_layout_new(
session=session,
settings_manager=settings_manager,
content=error_content
)
setup_toasts(app)
@rt('/toasting')
def get(session):
# Normally one toast is enough, this allows us to see
# different toast types in action.
add_toast(session, f"Toast is being cooked", "info")
add_toast(session, f"Toast is ready", "success")
add_toast(session, f"Toast is getting a bit crispy", "warning")
add_toast(session, f"Toast is burning!", "error")
return Titled("I like toast")
async def main():
logger.info(f" Starting FastHTML server on http://localhost:{APP_PORT}")
serve(port=APP_PORT)
if __name__ == "__main__":
# Start your application
logger.info("Application starting...")
try:
asyncio.run(main())
except KeyboardInterrupt:
logger.info("\nStopping application...")
except Exception as e:
logger.error(f"Error: {e}")