diff --git a/backend/Dockerfile b/backend/Dockerfile index 52e528a1..2daac8de 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -44,6 +44,9 @@ RUN /opt/venv/bin/pip install --no-cache-dir -r requirements.txt # Install required browsers for playwright RUN playwright install +# Install wkhtmltopdf +RUN apt-get install -y wkhtmltopdf + # Copy your application into the Docker image WORKDIR /opt/copilot/backend COPY . . diff --git a/backend/app/connectors/grafana/reporting/report-template-test.html b/backend/app/connectors/grafana/reporting/report-template-test.html new file mode 100644 index 00000000..b66a7937 --- /dev/null +++ b/backend/app/connectors/grafana/reporting/report-template-test.html @@ -0,0 +1,59 @@ + + + + + Grafana Dashboard Report + + + +
+

Screenshots

+ {% for panel in panels %} +
+ Screenshot +
Page {{ loop.index }}
+
+ {% endfor %} +
+ + diff --git a/backend/app/connectors/grafana/services/reporting.py b/backend/app/connectors/grafana/services/reporting.py index fb049c10..deefe68a 100644 --- a/backend/app/connectors/grafana/services/reporting.py +++ b/backend/app/connectors/grafana/services/reporting.py @@ -5,6 +5,11 @@ from playwright.async_api import async_playwright from loguru import logger import base64 +import os +import sys +import subprocess +import pdfkit +from jinja2 import Environment, FileSystemLoader from sqlalchemy.ext.asyncio import AsyncSession from app.connectors.grafana.schema.reporting import GenerateReportRequest, GenerateReportResponse, Base64Image from app.utils import get_connector_attribute @@ -13,6 +18,11 @@ from app.connectors.grafana.schema.reporting import GrafanaOrganizationDashboards from app.connectors.grafana.schema.reporting import GrafanaOrganizations from app.connectors.grafana.utils.universal import create_grafana_client +from reportlab.lib.pagesizes import letter +from reportlab.pdfgen import canvas +from reportlab.platypus import SimpleDocTemplate, Image +from reportlab.lib.units import inch +from pathlib import Path async def get_orgs() -> List[GrafanaOrganizations]: @@ -101,14 +111,62 @@ async def check_login_success(page): async def capture_screenshots(page, urls): base64_images = [] - for url in urls: + for i, url in enumerate(urls): await page.goto(url) await page.wait_for_load_state(state='networkidle') screenshot = await page.screenshot(type='png') base64_image = base64.b64encode(screenshot).decode('utf-8') - base64_images.append({"url": url, "base64_image": base64_image}) + width = await page.evaluate('window.innerWidth') + height = await page.evaluate('window.innerHeight') + base64_images.append({ + 'url': url, + 'base64_image': base64_image, + 'width': width, + 'height': height, + 'page_number': i + 1 + }) return base64_images +def get_wkhtmltopdf_path(): + if sys.platform == 'win32': + return 'C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe' + elif sys.platform == 'darwin': + return '/usr/local/bin/wkhtmltopdf' + else: + try: + # Try to find the path using 'which' command in Linux + path = subprocess.check_output(['which', 'wkhtmltopdf']) + return path.strip() + except Exception as e: + logger.error(f"Could not find wkhtmltopdf: {e}") + return None + +def create_pdf(html_string): + logger.info(f"Creating PDF from HTML: {html_string}") + wkhtmltopdf_path = get_wkhtmltopdf_path() + if wkhtmltopdf_path is None: + logger.error("Cannot create PDF without wkhtmltopdf") + return + config = pdfkit.configuration(wkhtmltopdf=wkhtmltopdf_path) + pdfkit.from_string(html_string, 'report.pdf', configuration=config) + + +def generate_html(base64_images): + # Load the template + templates_dir = Path(__file__).parent / '../reporting' + env = Environment(loader=FileSystemLoader(templates_dir)) + template = env.get_template('report-template-test.html') + + # Define the context + context = { + 'panels': base64_images, # Assuming this is adjusted to contain base64 encoded images + } + + # Render the template with the context + html_string = template.render(context) + + return html_string + async def generate_report( request: GenerateReportRequest, session: AsyncSession @@ -124,6 +182,8 @@ async def generate_report( return base64_images = await capture_screenshots(page, request.urls) await browser.close() + html_string = generate_html(base64_images) + create_pdf(html_string) return GenerateReportResponse( base64_images=[Base64Image(url=img['url'], base64_image=img['base64_image']) for img in base64_images], message="Report generated successfully", diff --git a/backend/report.pdf b/backend/report.pdf new file mode 100644 index 00000000..2dffe4fe Binary files /dev/null and b/backend/report.pdf differ diff --git a/backend/requirements.txt b/backend/requirements.txt index 6e0a4f80..d2889880 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -117,6 +117,7 @@ pytest-playwright==0.4.4 python-dateutil==2.8.2 python-dotenv==1.0.0 python-jose==3.3.0 +pdfkit==1.0.0 python-magic==0.4.27 python-multipart==0.0.6 pytz==2023.3.post1 @@ -128,6 +129,7 @@ regex==2023.10.3 reportlab==4.0.5 requests==2.31.0 requests-cache==1.1.0 +Jinja2=3.1.2 rfc3339-validator==0.1.4 rfc3986-validator==0.1.1 rich==13.6.0