# 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("undo_redo", "components.undo_redo", "UndoRedoApp") 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}")