feat: Implement initial Homelab Automation API v2 with new models, routes, and core architecture, including a SQLAlchemy model refactoring script.
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled

This commit is contained in:
2026-03-03 20:18:22 -05:00
parent 608f4b9197
commit c3cd7c2621
50 changed files with 2265 additions and 188 deletions
+12 -5
View File
@@ -191,7 +191,7 @@ class HelpMarkdownRenderer:
def _process_accordions(self, html: str) -> str:
"""Traite les accordéons."""
pattern = r':::accordion\s+(fa-[\w-]+)\s+(\w+)\s+([^\n]+)\n(.*?):::'
pattern = r':::accordion\s+((?:fa[bs]?\s+)?fa-[\w-]+)\s+(\w+)\s+([^\n]+)\n(.*?):::'
def replace_accordion(match):
icon = match.group(1)
@@ -385,18 +385,25 @@ class HelpMarkdownRenderer:
return '\n'.join(result)
def _process_headings(self, html: str) -> str:
"""Traite les titres H3 et H4."""
"""Traite les titres H1, H3 et H4."""
# H1
html = re.sub(
r'^#\s+([^\n{]+)(?:\s*\{#[^}]+\})?\s*$',
r'<h1 class="text-3xl font-bold mb-6 text-white">\1</h1>',
html,
flags=re.MULTILINE
)
# H3
html = re.sub(
r'^### ([^\n{]+)$',
r'^###\s+([^\n{]+)(?:\s*\{#[^}]+\})?\s*$',
r'<h3 class="font-semibold mb-4 text-purple-400">\1</h3>',
html,
flags=re.MULTILINE
)
# H4
html = re.sub(
r'^#### ([^\n]+)$',
r'<h4 class="font-semibold mb-2 flex items-center gap-2"><i class="fas fa-question-circle text-gray-400"></i>\1</h4>',
r'^####\s+([^\n{]+)(?:\s*\{#[^}]+\})?\s*$',
r'<h4 class="font-semibold mb-2 flex items-center gap-2">\1</h4>',
html,
flags=re.MULTILINE
)
+45 -19
View File
@@ -110,16 +110,41 @@ def emoji_to_png_bytes(emoji: str, size: int = 32) -> Optional[bytes]:
return None
import base64
def replace_emojis_with_images(text: str) -> str:
"""Remplace les emojis par des balises <img/> pour ReportLab."""
emoji_pattern = re.compile(
"["
"\U0001F600-\U0001F64F" # emoticons
"\U0001F300-\U0001F5FF" # symbols & pictographs
"\U0001F680-\U0001F6FF" # transport & map symbols
"\U0001F1E0-\U0001F1FF" # flags
"\U00002702-\U000027B0" # dingbats
"\U000024C2-\U0001F251" # enclosed characters
"\U0001F900-\U0001F9FF" # supplemental symbols
"\U0001FA00-\U0001FA6F" # chess symbols
"\U0001FA70-\U0001FAFF" # symbols extended
"\U00002600-\U000026FF" # misc symbols
"]"
)
def repl(match):
emoji = match.group()
png_bytes = emoji_to_png_bytes(emoji, size=24)
if png_bytes:
b64_img = base64.b64encode(png_bytes).decode('ascii')
# ReportLab image tag must be valid and size specified
return f'<img src="data:image/png;base64,{b64_img}" width="12" height="12"/>'
return emoji
return emoji_pattern.sub(repl, text)
def markdown_to_pdf_bytes(markdown_content: str, title: str = "Document") -> bytes:
"""Convertit du contenu Markdown en PDF.
"""Convertit du contenu Markdown en PDF."""
# Nettoyer les caractères de dessin de boîte pour Courier
markdown_content = markdown_content.replace('├──', '|--').replace('└──', '`--').replace('│', '|')
Args:
markdown_content: Contenu au format Markdown
title: Titre du document
Returns:
Bytes du PDF généré
"""
buffer = io.BytesIO()
doc = SimpleDocTemplate(
buffer,
@@ -130,7 +155,6 @@ def markdown_to_pdf_bytes(markdown_content: str, title: str = "Document") -> byt
bottomMargin=2*cm
)
# Styles
styles = getSampleStyleSheet()
title_style = ParagraphStyle(
@@ -282,19 +306,19 @@ def markdown_to_pdf_bytes(markdown_content: str, title: str = "Document") -> byt
# Titres
if line.startswith('# '):
emojis, text = extract_leading_emojis(line[2:].strip())
display_text = f"{emojis} {text}".strip() if emojis else text
elements.append(Paragraph(display_text, h1_style))
text = line[2:].strip()
text = replace_emojis_with_images(text)
elements.append(Paragraph(text, h1_style))
elif line.startswith('## '):
emojis, text = extract_leading_emojis(line[3:].strip())
display_text = f"{emojis} {text}".strip() if emojis else text
elements.append(Paragraph(display_text, h2_style))
text = line[3:].strip()
text = replace_emojis_with_images(text)
elements.append(Paragraph(text, h2_style))
elif line.startswith('### '):
emojis, text = extract_leading_emojis(line[4:].strip())
display_text = f"{emojis} {text}".strip() if emojis else text
elements.append(Paragraph(display_text, h3_style))
text = line[4:].strip()
text = replace_emojis_with_images(text)
elements.append(Paragraph(text, h3_style))
elif line.startswith('#### '):
text = line[5:].strip()
text = replace_emojis_with_images(line[5:].strip())
elements.append(Paragraph(text, h4_style))
# Listes à puces
@@ -303,6 +327,7 @@ def markdown_to_pdf_bytes(markdown_content: str, title: str = "Document") -> byt
# Convertir le markdown basique
text = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', text)
text = re.sub(r'`(.+?)`', r'<font face="Courier">\1</font>', text)
text = replace_emojis_with_images(text)
elements.append(Paragraph(f"• {text}", bullet_style))
# Listes numérotées
@@ -333,6 +358,7 @@ def markdown_to_pdf_bytes(markdown_content: str, title: str = "Document") -> byt
text = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', text)
text = re.sub(r'\*(.+?)\*', r'<i>\1</i>', text)
text = re.sub(r'`(.+?)`', r'<font face="Courier" color="#c7254e">\1</font>', text)
text = replace_emojis_with_images(text)
elements.append(Paragraph(text, body_style))
else: