302 lines
11 KiB
Python
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}")
|