Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
front_end.py
Go to the documentation of this file.
1r"""
2# +==== BEGIN CatFeeder =================+
3# LOGO:
4# ..............(..../\
5# ...............)..(.')
6# ..............(../..)
7# ...............\‍(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
10# animals/cats
11# /STOP
12# PROJECT: CatFeeder
13# FILE: front_end.py
14# CREATION DATE: 24-01-2026
15# LAST Modified: 15:47:54 18-02-2026
16# DESCRIPTION:
17# This is the project in charge of making the connected cat feeder project work.
18# /STOP
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE: This is the python file in charge of loading and providing the ressources required for loading a barebones front-end.
21# // AR
22# +==== END CatFeeder =================+
23"""
24
25import os
26from typing import TYPE_CHECKING, Dict, Union
27from datetime import datetime, timedelta
28from dataclasses import dataclass
29from enum import Enum
30
31from pathlib import Path
32from fastapi import Response, Request
33from display_tty import Disp, initialise_logger
34
35from ...utils import CONST
36from ...core import RuntimeManager, RI
37from ...http_codes import HCI, HttpDataTypes
38from ...path_manager import PathManager, decorators
39
40if TYPE_CHECKING:
41 from ...sql import SQL
42 from ...server_header import ServerHeaders
43 from ...boilerplates import BoilerplateIncoming, BoilerplateResponses, BoilerplateNonHTTP
44
45CACHE_LIFETIME: timedelta = timedelta(seconds=CONST.FRONT_END_ASSETS_REFRESH)
46
47
48@dataclass
50 """The cache node class
51 """
52 data: str = ""
53 timestamp: datetime = datetime.now() + CACHE_LIFETIME
54 source: str = ""
55
56
57class Pages(Enum):
58 DEFAULT = "default"
59 LOGIN = "login"
60 DASHBOARD = "dashboard"
61 LOGOUT = "logout"
62
63
65 """
66 Front-end manager for serving static assets and HTML pages.
67 """
68
69 disp: Disp = initialise_logger(__qualname__, False)
70
71 def __init__(self, error: int = 84, success: int = 0, debug: bool = False) -> None:
72 """_summary_
73 """
74 # ------------------------ The logging function ------------------------
75 self.disp.update_disp_debug(debug)
76 self.disp.log_debug("Initialising...")
77 # -------------------------- Inherited values --------------------------
78 self.error: int = error
79 self.success: int = success
80 self.debug: bool = debug
81 self.runtime_manager: RuntimeManager = RI
82 # ------------------------ General information -------------------------
83 self.author: str = "Henry Letellier"
84 self.year: str = str(datetime.now().year)
85 # ------------------------- Endpoint prefixes --------------------------
86 self.front_end: str = "/front-end"
87 self.front_end_login: str = f"{self.front_end}/"
88 self.front_end_dashboard: str = f"{self.front_end}/dashboard"
89 self.front_end_logout: str = f"{self.front_end}/logout"
90 self.front_end_assets: str = f"{self.front_end}/assets"
91 # ====================== JS asset files
92 self.front_end_assets_js: str = f"{self.front_end_assets}/js"
93 self.front_end_assets_js_modules: str = f"{self.front_end_assets_js}/modules"
94 self.front_end_assets_js_theme: str = f"{self.front_end_assets_js}/theme"
95 self.front_end_assets_js_module_cookies: str = f"{self.front_end_assets_js_modules}/cookies.mjs"
96 self.front_end_assets_js_module_indexdb: str = f"{self.front_end_assets_js_modules}/indexeddb_manager.mjs"
97 self.front_end_assets_js_module_querier: str = f"{self.front_end_assets_js_modules}/querier.mjs"
98 self.front_end_assets_js_module_general: str = f"{self.front_end_assets_js_modules}/general.mjs"
99 self.front_end_assets_js_theme_pico: str = f"{self.front_end_assets_js_theme}/minimal-theme-switcher.min.js"
100 self.front_end_assets_js_dashboard: str = f"{self.front_end_assets_js}/dashboard.js"
101 self.front_end_assets_js_login: str = f"{self.front_end_assets_js}/login.js"
102 self.front_end_assets_js_logout: str = f"{self.front_end_assets_js}/logout.js"
103 # ====================== CSS asset files
104 self.front_end_assets_css: str = f"{self.front_end_assets}/css"
105 self.front_end_assets_css_pico: str = f"{self.front_end_assets_css}/pico.min.css"
106 self.front_end_assets_css_custom: str = f"{self.front_end_assets_css}/custom.css"
107 self.front_end_assets_css_emoji_font: str = f"{self.front_end_assets_css}/noto-emoji.css"
108 # ====================== CSS asset files
109 self.front_end_assets_css_emoji_font_static: str = f"{self.front_end_assets_css}/static"
110 self.front_end_assets_css_emoji_font_static_regular: str = f"{self.front_end_assets_css_emoji_font_static}/NotoEmoji-Regular.ttf"
111 self.front_end_assets_css_emoji_font_static_light: str = f"{self.front_end_assets_css_emoji_font_static}/NotoEmoji-Light.ttf"
112 self.front_end_assets_css_emoji_font_static_medium: str = f"{self.front_end_assets_css_emoji_font_static}/NotoEmoji-Medium.ttf"
113 self.front_end_assets_css_emoji_font_static_semi_bold: str = f"{self.front_end_assets_css_emoji_font_static}/NotoEmoji-SemiBold.ttf"
114 self.front_end_assets_css_emoji_font_static_bold: str = f"{self.front_end_assets_css_emoji_font_static}/NotoEmoji-Bold.ttf"
115 # ====================== Image asset files
116 self.front_end_assets_images: str = f"{self.front_end_assets}/images"
117 self.front_end_assets_images_favicon: str = f"{self.front_end_assets_images}/favicon.ico"
118 self.front_end_assets_image_favicon_png: str = f"{self.front_end_assets_images}/favicon.png"
119 self.front_end_assets_images_logos: str = f"{self.front_end_assets_images}/logos"
120 # ====================== HTML asset files
121 self.front_end_assets_html_login: str = f"{self.front_end_assets}/login.html"
122 self.front_end_assets_html_logout: str = f"{self.front_end_assets}/logout.html"
123 self.front_end_assets_html_dashboard: str = f"{self.front_end_assets}/dashboard.html"
124 # ---------------------------- Source files ----------------------------
125 # ======================= CSS Files
126 self.source_css_pico: str = str(
127 CONST.STYLE_DIRECTORY / "pico.classless.min.css"
128 )
129 self.source_css_custom: str = str(
130 CONST.STYLE_DIRECTORY / "style.css"
131 )
132 self.source_css_emoji_font: str = str(
133 CONST.STYLE_DIRECTORY / "Noto_Emoji" / "noto-emoji.css"
134 )
135 # ======================= Font Files
137 CONST.STYLE_DIRECTORY / "Noto_Emoji" / "static" / "NotoEmoji-Regular.ttf"
138 )
140 CONST.STYLE_DIRECTORY / "Noto_Emoji" / "static" / "NotoEmoji-Light.ttf"
141 )
143 CONST.STYLE_DIRECTORY / "Noto_Emoji" / "static" / "NotoEmoji-Medium.ttf"
144 )
146 CONST.STYLE_DIRECTORY / "Noto_Emoji" / "static" / "NotoEmoji-SemiBold.ttf"
147 )
149 CONST.STYLE_DIRECTORY / "Noto_Emoji" / "static" / "NotoEmoji-Bold.ttf"
150 )
151 # ======================= Javascript Modules
152 self.source_js_module: Path = CONST.JS_DIRECTORY / "modules"
153 self.source_js_theme: Path = CONST.JS_DIRECTORY / "theme"
155 self.source_js_module / "cookies.mjs"
156 )
158 self.source_js_module / "indexeddb_manager.mjs"
159 )
161 self.source_js_module / "querier.mjs"
162 )
164 self.source_js_module / "general.mjs"
165 )
166 # ======================= Javascript Theme
167 self.source_js_theme_pico: str = str(
168 self.source_js_theme / "minimal-theme-switcher.min.js"
169 )
170 # ======================= Javascript Files
171 self.source_js_dashboard: str = str(
172 CONST.JS_DIRECTORY / "dashboard.js"
173 )
174 self.source_js_login: str = str(
175 CONST.JS_DIRECTORY / "login.js"
176 )
177 self.source_js_logout: str = str(
178 CONST.JS_DIRECTORY / "logout.js"
179 )
180 # ======================= Html Files
181 self.source_html_login: str = str(
182 CONST.HTML_DIRECTORY / "login.html"
183 )
184 self.source_html_dashboard: str = str(
185 CONST.HTML_DIRECTORY / "dashboard.html"
186 )
187 self.source_html_logout: str = str(
188 CONST.HTML_DIRECTORY / "logout.html"
189 )
190 # ======================= File List
191 self.source_files: Dict[str, str] = {
206 }
207 # -------------------------- Shared instances --------------------------
208 self.boilerplate_incoming_initialised: "BoilerplateIncoming" = self.runtime_manager.get(
209 "BoilerplateIncoming")
210 self.boilerplate_responses_initialised: "BoilerplateResponses" = self.runtime_manager.get(
211 "BoilerplateResponses")
212 self.boilerplate_non_http_initialised: "BoilerplateNonHTTP" = self.runtime_manager.get(
213 "BoilerplateNonHTTP")
214 self.database_link: "SQL" = self.runtime_manager.get("SQL")
215 self.server_headers_initialised: "ServerHeaders" = self.runtime_manager.get(
216 "ServerHeaders")
217 # ----------------------------- file caches ----------------------------
218 self.file_cache: Dict[str, CacheEntry] = {}
219 self.key_data: str = "data"
220 self.key_timestamp: str = "timestamp"
221 self.key_source: str = "source"
222 self.cache_expiration: timedelta = CACHE_LIFETIME
223 # ---------------------------- Finalisation ----------------------------
224 self.disp.log_debug("Initialised")
225
226 # ---------------------------- Cache management ----------------------------
227
228 def _get_client_host(self, request: Request) -> str:
229 """Get the client's host from the request.
230
231 Args:
232 request: FastAPI Request object.
233 Returns:
234 str: Client's host as a string.
235 """
236 # if request.client:
237 # self.disp.log_debug(f"Client host: {request.client.host}")
238 # return request.client.host
239 return "127.0.0.1"
240
241 def _get_file_content(self, file_path: str) -> str:
242 """Read the content of a file.
243
244 Args:
245 file_path: Path to the file.
246 Returns:
247 str: Content of the file as a string.
248 """
249 with open(file_path, "r", encoding="utf-8") as file:
250 content = file.read()
251 return content
252
253 def _load_cache(self, expiration: timedelta = CACHE_LIFETIME) -> None:
254 """Load the file cache for static assets.
255 """
256 self.disp.log_debug("Loading front-end file cache...")
257 total_files = 0
258 self.cache_expiration = expiration
259 for endpoint, path in self.source_files.items():
260 self.disp.log_debug(
261 f"Caching front-end file: {path} for endpoint: {endpoint}"
262 )
263 expiration_date = datetime.now() + expiration
264 try:
265 file_content = self._get_file_content(path)
266 except (OSError, UnicodeDecodeError) as e:
267 self.disp.log_error(f"Failed to load file {path}: {e}")
268 continue
269 self.file_cache[endpoint] = CacheEntry(
270 data=file_content,
271 timestamp=expiration_date,
272 source=path
273 )
274 total_files += 1
275 self.disp.log_debug(
276 f"Cached front-end file: {path} for endpoint: {endpoint} until {expiration_date}"
277 )
278 self.disp.log_debug(f"{total_files} Front-end file cache loaded.")
279
280 def _refresh_cache(self) -> None:
281 """Refresh the file cache for static assets.
282 """
283 self.disp.log_debug("Refreshing front-end file cache...")
284 cache_refreshed = 0
285 skipped = 0
286 for endpoint, cache_entry in self.file_cache.items():
287 if datetime.now() >= cache_entry.timestamp:
288 self.disp.log_debug(
289 f"Refreshing cache for front-end file: {cache_entry.source} at endpoint: {endpoint}"
290 )
291 expiration_date = datetime.now() + self.cache_expiration
292 try:
293 file_content = self._get_file_content(cache_entry.source)
294 except (OSError, UnicodeDecodeError) as e:
295 self.disp.log_error(
296 f"Failed to load file {cache_entry.source}: {e}"
297 )
298 continue
299 self.file_cache[endpoint] = CacheEntry(
300 data=file_content,
301 timestamp=expiration_date,
302 source=cache_entry.source
303 )
304 cache_refreshed += 1
305 self.disp.log_debug(
306 f"Refreshed cache for front-end file: {cache_entry.source} at endpoint: {endpoint} until {expiration_date}"
307 )
308 else:
309 skipped += 1
310 self.disp.log_debug(
311 f"Refreshed {cache_refreshed} front-end file caches."
312 )
313 self.disp.log_debug(f"Skipped {skipped} front-end file caches.")
314
315 def _ressource_not_found_response(self, path: str, title: str) -> Response:
316 """Generate a 404 response for missing resources.
317
318 Args:
319 path: Path to the missing resource.
320 Returns:
321 Response: FastAPI Response with 404 error.
322 """
323 body = self.boilerplate_responses_initialised.build_response_body(
324 title=title,
325 message=f"The requested resource at path {path} was not found.",
326 resp="not_found",
327 token=None,
328 error=True
329 )
330 return HCI.not_found(body, content_type=HttpDataTypes.JSON, headers=self.server_headers_initialised.for_json())
331
332 def _get_cache(self, path: str) -> Union[str, Response]:
333 """Refresh the file cache for static assets.
334 """
335 self.disp.log_debug(f"Retrieving front-end file from cache: {path}")
336 self._refresh_cache()
337 if path not in self.file_cache:
338 self.disp.log_debug(f"Front-end file not found in cache: {path}")
339 return self._ressource_not_found_response(path, "Resource Not Found")
340 data = self.file_cache[path]
341 if not data.data:
342 self.disp.log_debug(f"Front-end file cache empty for: {path}")
344 path, "Resource Not Found"
345 )
346 self.disp.log_debug(f"Retrieved front-end file from cache: {path}")
347 return data.data
348 # -------------------------- End Cache management --------------------------
349 # ------------------------------ HTML Snippets -----------------------------
350
351 def _get_headers(self, page_title: str) -> str:
352 """Generate HTML headers for front-end pages.
353
354 Args:
355 page_title: Title of the HTML page.
356 Returns:
357 str: HTML headers as a string.
358 """
359 app_name: str = self.server_headers_initialised.app_name
360 verification: str = CONST.GOOGLE_SITE_VERIFICATION_CODE
361 headers = f"""<!DOCTYPE html>
362<html lang="en">
363<head>
364 <meta lang="eng">
365 <meta charset="UTF-8">
366 <meta name="Language" CONTENT="en,fr" />
367 <meta name="publisher" content="{self.author}" />
368 <meta http-equiv="pragma" content="cache" />
369 <meta http-equiv="Cache-control" content="public" />
370 <meta name="googlebot" content="index,follow,snippet" />
371 <meta name="google" content="translate,sitelinkssearchbox" />
372 <link rel="stylesheet" href="{self.front_end_assets_css_pico}">
373 <link rel="stylesheet" href="{self.front_end_assets_css_custom}">
374 <link rel="stylesheet" href="{self.front_end_assets_css_emoji_font}">
375 <meta name="google-site-verification" content="{verification}" />
376 <meta name="copyright" content=\"&copy; {self.author} {self.year}"/>
377 <meta name="viewport" content="width=device-width, initial-scale=1.0">
378 <meta name="robots" content="index,follow,max-image-preview:standard" />
379 <link rel="icon" type="image/png" href="{self.front_end_assets_image_favicon_png}" />
380 <meta name="Index" content="This is the page named: {page_title} of the server {app_name}." />
381 <link rel="shortcut icon" type="image/x-icon" href="{self.front_end_assets_images_favicon}" />
382</head>
383"""
384 return headers
385
386 def _get_theme_toggler(self) -> str:
387 """Generate HTML theme toggler for front-end pages.
388
389 Returns:
390 str: HTML theme toggler as a string.
391 """
392 toggler = """<div class="theme-toggler">
393 <label for="theme-select">Theme:</label>
394 <select id="theme-select" aria-label="Theme selector">
395 <option value="system">System</option>
396 <option value="light">Light</option>
397 <option value="dark">Dark</option>
398 </select>
399</div>
400"""
401 return toggler
402
403 def _get_heading(self, page_title: str, show_logout: bool = False) -> str:
404 """Generate HTML heading for front-end pages.
405
406 Args:
407 page_title: Title of the page.
408 show_logout: Whether to show the logout button.
409 Returns:
410 str: HTML heading as a string.
411 """
412 theme_toggle = self._get_theme_toggler()
413 logout_button = ""
414 if show_logout:
415 logout_button = '<div class="logout-container"><button onclick="logout()">Logout</button></div>'
416 heading = f"""<header class="page-header">
417 <h1 class="page-title">{self.server_headers_initialised.app_name} - {page_title}</h1>
418 <div class="theme-container">{theme_toggle}</div>
419 {logout_button}
420</header>
421"""
422 return heading
423
424 def _get_footers(self, page_type: Pages = Pages.DEFAULT, client_host: str = "") -> str:
425 """Generate HTML footers for front-end pages.
426
427 Args:
428 page_type: Type of the page (Pages enum).
429 Returns:
430 str: HTML footers as a string.
431 """
432 if not client_host:
433 host: str = self.server_headers_initialised.host
434 else:
435 host: str = client_host
436 if host == "0.0.0.0":
437 host = "http://127.0.0.1"
438 if not host.startswith("http"):
439 host = f"http://{host}"
440 port: int = self.server_headers_initialised.port
441 if port == 0:
442 port = -1
443 name: str = self.server_headers_initialised.app_name.lower().replace(" ", "_")
444 page_scripts = ""
445 if page_type == Pages.LOGIN:
446 page_scripts += f'<script type="text/JavaScript" src="{self.front_end_assets_js_login}" data-dashboard-url="{self.front_end_dashboard}"></script>'
447 if page_type == Pages.DASHBOARD:
448 page_scripts += f'<script type="text/JavaScript" src="{self.front_end_assets_js_dashboard}" data-login-url="{self.front_end_login}" data-logout-url="{self.front_end_logout}"></script>'
449 if page_type == Pages.LOGOUT:
450 page_scripts += f'<script type="text/JavaScript" src="{self.front_end_assets_js_logout}" data-login-url="{self.front_end_login}"></script>'
451 footers = f"""<div class="footer">
452 <hr/>
453 <p>&copy; {self.author} {self.year}</p>
454 <script type="module" src="{self.front_end_assets_js_module_cookies}"></script>
455 <script type="module" src="{self.front_end_assets_js_module_indexdb}" data-db-name="{name}" data-store-name="keyValueStore"></script>
456 <script type="module" src="{self.front_end_assets_js_module_querier}" data-api-url="{host}" data-api-port="{port}"></script>
457 <script id="general-module" type="module" src="{self.front_end_assets_js_module_general}" data-theme-cookie-name="theme" data-cookie-expires-days="365"></script>
458 <script type="text/JavaScript" src="{self.front_end_assets_js_theme_pico}"></script>
459 {page_scripts}
460 <script type="module">
461 const moduleScript = document.getElementById('general-module');
462 const opts = moduleScript ? moduleScript.dataset : {"{}"};
463 window.general_scripts.initThemeToggler(opts);
464 </script>
465</div>"""
466 return footers
467 # --------------------------- End HTML Snippets ----------------------------
468 # ------------------------------- HTML Pages -------------------------------
469
470 def serve_login(self, request: Request) -> Response:
471 """Serve the login page.
472
473 Returns:
474 Response: FastAPI Response with login HTML content.
475 """
476 client_host = self._get_client_host(request)
477 page_title = "Login"
478 page_header = self._get_headers(page_title)
479 page_heading = self._get_heading(page_title)
480 page_body = self._get_cache(self.front_end_assets_html_login)
481 page_footer = self._get_footers(Pages.LOGIN, client_host)
482 page_content = f"""{page_header}
483<body>
484 {page_heading}
485 {page_body}
486 {page_footer}
487</body>
488</html>
489"""
490 return HCI.success(page_content, content_type=HttpDataTypes.HTML, headers=self.server_headers_initialised.for_html())
491
492 def serve_dashboard(self, request: Request) -> Response:
493 """Serve the dashboard page.
494
495 Returns:
496 Response: FastAPI Response with dashboard HTML content.
497 """
498 client_host: str = self._get_client_host(request)
499 page_title = "Dashboard"
500 page_header = self._get_headers(page_title)
501 page_heading = self._get_heading(page_title, show_logout=True)
502 page_body = self._get_cache(self.front_end_assets_html_dashboard)
503 page_footer = self._get_footers(Pages.DASHBOARD, client_host)
504 page_content = f"""{page_header}
505<body>
506 {page_heading}
507 {page_body}
508 {page_footer}
509</body>
510</html>
511"""
512 return HCI.success(page_content, content_type=HttpDataTypes.HTML, headers=self.server_headers_initialised.for_html())
513
514 def serve_logout(self, request: Request) -> Response:
515 """Serve the logout page.
516
517 Returns:
518 Response: FastAPI Response with logout HTML content.
519 """
520 client_host: str = self._get_client_host(request)
521 page_title = "Logout"
522 page_header = self._get_headers(page_title)
523 page_heading = self._get_heading(page_title)
524 page_body = self._get_cache(self.front_end_assets_html_logout)
525 page_footer = self._get_footers(Pages.LOGOUT, client_host)
526 page_content = f"""{page_header}
527<body>\n
528 {page_heading}
529 {page_body}
530 {page_footer}
531</body>
532</html>
533"""
534 return HCI.success(page_content, content_type=HttpDataTypes.HTML, headers=self.server_headers_initialised.for_html())
535 # ----------------------------- End HTML Pages -----------------------------
536 # ---------------------------- Assets endpoints ----------------------------
537
538 def get_pico(self) -> Response:
539 """Get the Pico CSS content.
540
541 Returns:
542 Response: FastAPI Response with Pico CSS content.
543 """
544 css_content = self._get_cache(self.front_end_assets_css_pico)
545 if isinstance(css_content, Response):
546 return css_content
547 return HCI.success(css_content, content_type=HttpDataTypes.CSS, headers=self.server_headers_initialised.for_css())
548
549 def get_custom_css(self) -> Response:
550 """Get the Pico CSS content.
551
552 Returns:
553 Response: FastAPI Response with Pico CSS content.
554 """
555 css_content = self._get_cache(self.front_end_assets_css_custom)
556 if isinstance(css_content, Response):
557 return css_content
558 return HCI.success(css_content, content_type=HttpDataTypes.CSS, headers=self.server_headers_initialised.for_css())
559
560 def get_css_emoji_font(self) -> Response:
561 """Get the Emoji font CSS content.
562
563 Returns:
564 Response: FastAPI Response with Emoji font CSS content.
565 """
566 css_content = self._get_cache(self.front_end_assets_css_emoji_font)
567 if isinstance(css_content, Response):
568 return css_content
569 return HCI.success(css_content, content_type=HttpDataTypes.CSS, headers=self.server_headers_initialised.for_css())
570
571 def get_css_emoji_regular(self) -> Response:
572 """Get the Emoji regular font file
573
574 Returns:
575 Response: FastAPI Response with the Emoji font file
576 """
577 file_content = self._get_cache(
579 if isinstance(file_content, Response):
580 if os.path.isfile(self.source_css_emoji_font_regular):
581 return HCI.success(self.source_css_emoji_font_regular, content_type=HttpDataTypes.TTF, headers=self.server_headers_initialised.for_file())
582 return file_content
583 return HCI.success(file_content, content_type=HttpDataTypes.TTF, headers=self.server_headers_initialised.for_file())
584
585 def get_css_emoji_light(self) -> Response:
586 """Get the Emoji light font file
587
588 Returns:
589 Response: FastAPI Response with the Emoji font file
590 """
591 file_content = self._get_cache(
593 )
594 if isinstance(file_content, Response):
595 if os.path.isfile(self.source_css_emoji_font_light):
596 return HCI.success(self.source_css_emoji_font_light, content_type=HttpDataTypes.TTF, headers=self.server_headers_initialised.for_file())
597 return file_content
598 return HCI.success(file_content, content_type=HttpDataTypes.TTF, headers=self.server_headers_initialised.for_file())
599
600 def get_css_emoji_medium(self) -> Response:
601 """Get the Emoji medium font file
602
603 Returns:
604 Response: FastAPI Response with the Emoji font file
605 """
606 file_content = self._get_cache(
608 if isinstance(file_content, Response):
609 if os.path.isfile(self.source_css_emoji_font_medium):
610 return HCI.success(self.source_css_emoji_font_medium, content_type=HttpDataTypes.TTF, headers=self.server_headers_initialised.for_file())
611 return file_content
612 return HCI.success(file_content, content_type=HttpDataTypes.TTF, headers=self.server_headers_initialised.for_file())
613
614 def get_css_emoji_semi_bold(self) -> Response:
615 """Get the Emoji regular font file
616
617 Returns:
618 Response: FastAPI Response with the Emoji font file
619 """
620 file_content = self._get_cache(
622 )
623 if isinstance(file_content, Response):
624 if os.path.isfile(self.source_css_emoji_font_semi_bold):
625 return HCI.success(self.source_css_emoji_font_semi_bold, content_type=HttpDataTypes.TTF, headers=self.server_headers_initialised.for_file())
626 return file_content
627 return HCI.success(file_content, content_type=HttpDataTypes.TTF, headers=self.server_headers_initialised.for_file())
628
629 def get_css_emoji_bold(self) -> Response:
630 """Get the Emoji regular font file
631
632 Returns:
633 Response: FastAPI Response with the Emoji font file
634 """
635 file_content = self._get_cache(
637 if isinstance(file_content, Response):
638 if os.path.isfile(self.source_css_emoji_font_bold):
639 return HCI.success(self.source_css_emoji_font_bold, content_type=HttpDataTypes.TTF, headers=self.server_headers_initialised.for_file())
640 return file_content
641 return HCI.success(file_content, content_type=HttpDataTypes.TTF, headers=self.server_headers_initialised.for_file())
642
643 def get_cookies_module(self) -> Response:
644 """Get the cookies JavaScript module content.
645
646 Returns:
647 Response: FastAPI Response with cookies JS module content.
648 """
649 js_content = self._get_cache(self.front_end_assets_js_module_cookies)
650 if isinstance(js_content, Response):
651 return js_content
652 return HCI.success(js_content, content_type=HttpDataTypes.JAVASCRIPT, headers=self.server_headers_initialised.for_javascript())
653
654 def get_indexeddb_module(self) -> Response:
655 """Get the IndexedDB JavaScript module content.
656
657 Returns:
658 Response: FastAPI Response with IndexedDB JS module content.
659 """
660 js_content = self._get_cache(self.front_end_assets_js_module_indexdb)
661 if isinstance(js_content, Response):
662 return js_content
663 return HCI.success(js_content, content_type=HttpDataTypes.JAVASCRIPT, headers=self.server_headers_initialised.for_javascript())
664
665 def get_querier_module(self) -> Response:
666 """Get the Querier JavaScript module content.
667
668 Returns:
669 Response: FastAPI Response with Querier JS module content.
670 """
671 js_content = self._get_cache(self.front_end_assets_js_module_querier)
672 if isinstance(js_content, Response):
673 return js_content
674 return HCI.success(js_content, content_type=HttpDataTypes.JAVASCRIPT, headers=self.server_headers_initialised.for_javascript())
675
676 def get_general_module(self) -> Response:
677 """Get the General JavaScript module content.
678 Returns:
679 Response: FastAPI Response with General JS module content.
680 """
681 js_content = self._get_cache(self.front_end_assets_js_module_general)
682 if isinstance(js_content, Response):
683 return js_content
684 return HCI.success(js_content, content_type=HttpDataTypes.JAVASCRIPT, headers=self.server_headers_initialised.for_javascript())
685
686 def get_pico_theme(self) -> Response:
687 """Get the Pico theme JavaScript content.
688
689 Returns:
690 Response: FastAPI Response with Pico theme JS content.
691 """
692 js_content = self._get_cache(self.front_end_assets_js_theme_pico)
693 if isinstance(js_content, Response):
694 return js_content
695 return HCI.success(js_content, content_type=HttpDataTypes.JAVASCRIPT, headers=self.server_headers_initialised.for_javascript())
696
697 def get_dashboard_js(self) -> Response:
698 """Get the dashboard JavaScript content.
699
700 Returns:
701 Response: FastAPI Response with dashboard JS content.
702 """
703 js_content = self._get_cache(self.front_end_assets_js_dashboard)
704 if isinstance(js_content, Response):
705 return js_content
706 return HCI.success(js_content, content_type=HttpDataTypes.JAVASCRIPT, headers=self.server_headers_initialised.for_javascript())
707
708 def get_login_js(self) -> Response:
709 """Get the login JavaScript content.
710
711 Returns:
712 Response: FastAPI Response with login JS content.
713 """
714 js_content = self._get_cache(self.front_end_assets_js_login)
715 if isinstance(js_content, Response):
716 return js_content
717 return HCI.success(js_content, content_type=HttpDataTypes.JAVASCRIPT, headers=self.server_headers_initialised.for_javascript())
718
719 def get_logout_js(self) -> Response:
720 """Get the logout JavaScript content.
721
722 Returns:
723 Response: FastAPI Response with logout JS content.
724 """
725 js_content = self._get_cache(self.front_end_assets_js_logout)
726 if isinstance(js_content, Response):
727 return js_content
728 return HCI.success(js_content, content_type=HttpDataTypes.JAVASCRIPT, headers=self.server_headers_initialised.for_javascript())
729
730 def get_favicon_ico(self) -> Response:
731 """Get the favicon ICO content.
732
733 Returns:
734 Response: FastAPI Response with favicon ICO content.
735 """
736 icon = CONST.ICON_PATH
737 self.disp.log_debug(f"Favicon path: {icon}")
738 if os.path.isfile(icon):
739 return HCI.success(icon, content_type=HttpDataTypes.XICON)
740 return HCI.not_found("Icon not found in the expected directory", content_type=HttpDataTypes.TEXT)
741
742 def get_favicon_png(self) -> Response:
743 """Get the favicon PNG content.
744
745 Returns:
746 Response: FastAPI Response with favicon PNG content.
747 """
748 icon = CONST.PNG_ICON_PATH
749 self.disp.log_debug(f"Static logo path: {icon}")
750 if os.path.isfile(icon):
751 return HCI.success(icon, content_type=HttpDataTypes.PNG, headers=self.server_headers_initialised.for_image())
752 return HCI.not_found("Icon not found in the expected directory", content_type=HttpDataTypes.TEXT)
753 # ---------------------------- Assets endpoints ----------------------------
754 # ----------------------------- Path Injection -----------------------------
755
756 def _inject_assets(self, path_manager: "PathManager") -> None:
757 """Inject front-end asset paths into the PathManager.
758
759 Args:
760 path_manager: Instance of PathManager to inject paths into.
761 """
763 assets = {
782 }
783 for endpooint, handler in assets.items():
784 path_manager.add_path_if_not_exists(
785 endpooint, handler, "GET",
786 decorators=[decorators.public_endpoint(),
787 decorators.front_end_assets_endpoint]
788 )
789
790 def inject_paths(self, path_manager: "PathManager") -> None:
791 """Inject front-end paths into the PathManager.
792
793 Args:
794 path_manager: Instance of PathManager to inject paths into.
795 """
796 path_manager.add_path_if_not_exists(
797 f"{self.front_end_login}", self.serve_loginserve_login, "GET",
798 decorators=[decorators.public_endpoint(),
799 decorators.front_end_endpoint]
800 )
801 path_manager.add_path_if_not_exists(
802 f"{self.front_end_dashboard}", self.serve_dashboardserve_dashboard, "GET",
803 decorators=[decorators.auth_endpoint(),
804 decorators.front_end_endpoint]
805 )
806 path_manager.add_path_if_not_exists(
807 f"{self.front_end_logout}", self.serve_logoutserve_logout, "GET",
808 decorators=[decorators.public_endpoint(),
809 decorators.front_end_endpoint]
810 )
811 self._inject_assets(path_manager)
812 # --------------------------- End Path Injection ---------------------------
None _load_cache(self, timedelta expiration=CACHE_LIFETIME)
Definition front_end.py:253
str _get_heading(self, str page_title, bool show_logout=False)
Definition front_end.py:403
str _get_footers(self, Pages page_type=Pages.DEFAULT, str client_host="")
Definition front_end.py:424
None __init__(self, int error=84, int success=0, bool debug=False)
Definition front_end.py:71