From adfc75ec350c147f9102842b08bf9c4b537526ab Mon Sep 17 00:00:00 2001 From: snapier Date: Tue, 10 Feb 2026 18:55:49 +0000 Subject: [PATCH] Upload files to "scripts" --- scripts/export_report.py | 302 +++++++++++++++++++++++++++++++++++++++ scripts/main.js | 176 +++++++++++++++++++++++ 2 files changed, 478 insertions(+) create mode 100644 scripts/export_report.py create mode 100644 scripts/main.js diff --git a/scripts/export_report.py b/scripts/export_report.py new file mode 100644 index 0000000..e553f57 --- /dev/null +++ b/scripts/export_report.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 +""" +Storage Saturation Report Export Script +Converts JSON report data to PDF or JPG format using weasyprint and Pillow +""" + +import sys +import json +import argparse +import os +from datetime import datetime + +try: + from weasyprint import HTML, CSS +except ImportError: + print("Error: weasyprint module not installed. Please install with: pip3 install weasyprint", file=sys.stderr) + sys.exit(1) + +try: + from PIL import Image + from pdf2image import convert_from_path +except ImportError: + print("Error: Pillow or pdf2image module not installed. Please install with: pip3 install Pillow pdf2image", file=sys.stderr) + sys.exit(1) + + +def generate_html_template(data): + """Generate HTML report from JSON data""" + + title = data.get('title', 'Storage Saturation Report') + date = data.get('date', datetime.now().strftime('%m/%d/%y, %I:%M %p')) + services = data.get('services', []) + + # Determine color class for available space + def get_available_class(available_bytes): + if available_bytes == 0: + return 'text-muted' + available_gb = available_bytes / (1024 * 1024 * 1024) + if available_gb < 5: + return 'text-danger' + elif available_gb < 50: + return 'text-warning' + return 'text-success' + + # Determine progress bar class + def get_progress_class(percent): + if percent >= 95: + return 'progress-bar-danger' + elif percent >= 85: + return 'progress-bar-warning' + elif percent >= 70: + return 'progress-bar-info' + return 'progress-bar-success' + + # Build table rows + table_rows = '' + if isinstance(services, dict) and 'error' in services: + table_rows = f''' + + + Error: {services['error']} + + + ''' + elif isinstance(services, list) and len(services) > 0: + for service in services: + host_name = service.get('host_name', 'N/A') + caption = service.get('service_description', service.get('name', 'N/A')) + disk_used = service.get('disk_used_display', 'N/A') + disk_available = service.get('disk_available_display', 'N/A') + disk_percent = service.get('disk_usage_percent', 0) + disk_percent_display = service.get('disk_percent_display', 'N/A') + + # Get color classes + available_bytes = service.get('disk_available_bytes', 0) + available_class = get_available_class(available_bytes) + progress_class = get_progress_class(disk_percent) + + # Build progress bar HTML + if disk_percent > 0 and disk_percent_display != 'N/A': + progress_bar = f''' +
+
+ {disk_percent_display} +
+
+ ''' + else: + progress_bar = 'N/A' + + # Determine text color for available + available_color = '#d9534f' if available_class == 'text-danger' else '#f0ad4e' if available_class == 'text-warning' else '#5cb85c' + + table_rows += f''' + + {host_name} + {caption} + {disk_used} + {disk_available} + {progress_bar} + + ''' + else: + table_rows = ''' + + + No data available + + + ''' + + html_template = f''' + + + + {title} + + + +
+

{title}

+

Summary of Tracked Storage: Volumes

+

Report Date: {date}

+

Ordered by: Disk Space Available - Ascending

+
+ + + + + + + + + + + + + {table_rows} + +
DISPLAY NAMECAPTIONDISK SPACE USEDDISK SPACE AVAILABLEPERCENT USED
+ + +''' + + return html_template + + +def convert_to_pdf(html_content, output_file): + """Convert HTML to PDF using weasyprint""" + try: + HTML(string=html_content).write_pdf(output_file) + return True + except Exception as e: + print(f"Error converting to PDF: {e}", file=sys.stderr) + return False + + +def convert_pdf_to_jpg(pdf_file, jpg_file): + """Convert PDF to JPG using pdf2image""" + try: + # Convert PDF to images (first page only) + images = convert_from_path(pdf_file, first_page=1, last_page=1, dpi=150) + if images: + # Save as JPG + images[0].save(jpg_file, 'JPEG', quality=95) + return True + return False + except Exception as e: + print(f"Error converting PDF to JPG: {e}", file=sys.stderr) + return False + + +def main(): + parser = argparse.ArgumentParser(description='Export Storage Saturation Report to PDF or JPG') + parser.add_argument('--input', required=True, help='Input JSON file path') + parser.add_argument('--output', required=True, help='Output file path') + parser.add_argument('--format', required=True, choices=['pdf', 'jpg'], help='Output format (pdf or jpg)') + + args = parser.parse_args() + + # Read JSON input + try: + with open(args.input, 'r', encoding='utf-8') as f: + data = json.load(f) + except FileNotFoundError: + print(f"Error: Input file not found: {args.input}", file=sys.stderr) + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in input file: {e}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error reading input file: {e}", file=sys.stderr) + sys.exit(1) + + # Generate HTML + html_content = generate_html_template(data) + + # Convert to PDF + if args.format == 'pdf': + if not convert_to_pdf(html_content, args.output): + sys.exit(1) + elif args.format == 'jpg': + # First convert to PDF, then to JPG + temp_pdf = args.output.replace('.jpg', '.pdf').replace('.jpeg', '.pdf') + if not convert_to_pdf(html_content, temp_pdf): + sys.exit(1) + + if not convert_pdf_to_jpg(temp_pdf, args.output): + # Clean up temp PDF + if os.path.exists(temp_pdf): + os.remove(temp_pdf) + sys.exit(1) + + # Clean up temp PDF + if os.path.exists(temp_pdf): + os.remove(temp_pdf) + + print(f"Successfully created {args.format.upper()}: {args.output}") + sys.exit(0) + + +if __name__ == '__main__': + main() + diff --git a/scripts/main.js b/scripts/main.js new file mode 100644 index 0000000..a19157d --- /dev/null +++ b/scripts/main.js @@ -0,0 +1,176 @@ +/** + * Storage Saturation Report - Main JavaScript + * Loads report via mode=getreport. PDF/Image export via client-side jsPDF + html2canvas (no XI Chromium pipeline). + */ + +$(document).ready(function() { + load_report(); + + $('#export-pdf-btn').on('click', function() { + exportToPdf(); + }); + + $('#export-image-btn').on('click', function() { + exportToImage(); + }); + + function load_report() { + var baseUrl = window.saturationreportBaseUrl || (window.location.pathname.replace(/\/[^/]*$/, '/index.php')); + var url = baseUrl + '?mode=getreport'; + + $.get(url, function(html) { + $('#report').html(html); + }).fail(function() { + $('#report').html( + '
' + + 'Error loading storage saturation report.' + + '
' + ); + }); + } + + function getComponentBase() { + return (window.saturationreportBaseUrl || '').replace(/\/[^/]*$/, ''); + } + + function loadScript(src, onload, onerror) { + var script = document.createElement('script'); + script.src = src; + script.onload = onload; + script.onerror = onerror || function() {}; + document.head.appendChild(script); + } + + function exportToPdf() { + var btn = $('#export-pdf-btn'); + var originalText = btn.html(); + btn.prop('disabled', true).html(' Generating...'); + + function doPdf() { + if (typeof html2canvas === 'undefined') { + var componentBase = getComponentBase(); + var localH2c = componentBase ? (componentBase + '/scripts/vendor/html2canvas.min.js') : ''; + var cdnH2c = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js'; + loadScript(localH2c || cdnH2c, function() { doPdf(); }, function() { + if (localH2c) { loadScript(cdnH2c, doPdf, fallbackPdf); return; } + fallbackPdf(); + }); + return; + } + if (typeof window.jspdf === 'undefined' || !window.jspdf.jsPDF) { + var componentBase = getComponentBase(); + var localJspdf = componentBase ? (componentBase + '/scripts/vendor/jspdf.umd.min.js') : ''; + var cdnJspdf = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; + loadScript(localJspdf || cdnJspdf, doPdf, function() { + if (localJspdf) loadScript(cdnJspdf, doPdf, fallbackPdf); + else fallbackPdf(); + }); + return; + } + var container = document.getElementById('saturationreport-container'); + if (!container) container = document.getElementById('report'); + if (!container) { + alert('Report container not found'); + btn.prop('disabled', false).html(originalText); + return; + } + html2canvas(container, { + backgroundColor: '#ffffff', + scale: 2, + logging: false, + useCORS: true + }).then(function(canvas) { + try { + var JsPDF = window.jspdf.jsPDF; + var doc = new JsPDF('p', 'mm', 'a4'); + var pageW = doc.internal.pageSize.getWidth(); + var pageH = doc.internal.pageSize.getHeight(); + var margin = 10; + var maxW = pageW - 2 * margin; + var maxH = pageH - 2 * margin; + var imgW = maxW; + var imgH = (canvas.height * maxW) / canvas.width; + if (imgH > maxH) { + imgH = maxH; + imgW = (canvas.width * maxH) / canvas.height; + } + doc.addImage(canvas.toDataURL('image/png'), 'PNG', margin, margin, imgW, imgH); + doc.save('storage-saturation-report-' + new Date().getTime() + '.pdf'); + } catch (err) { + console.error('PDF export error:', err); + alert('Failed to generate PDF. Try Print to PDF from the browser.'); + } + btn.prop('disabled', false).html(originalText); + }).catch(function(err) { + console.error('Capture error:', err); + fallbackPdf(); + }); + } + + function fallbackPdf() { + btn.prop('disabled', false).html(originalText); + window.print(); + } + + doPdf(); + } + + function exportToImage() { + if (typeof html2canvas === 'undefined') { + var componentBase = (window.saturationreportBaseUrl || '').replace(/\/[^/]*$/, ''); + var localSrc = componentBase ? (componentBase + '/scripts/vendor/html2canvas.min.js') : ''; + var cdnSrc = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js'; + var script = document.createElement('script'); + script.src = localSrc || cdnSrc; + script.onload = function() { captureImage(); }; + script.onerror = function() { + if (localSrc && script.src === localSrc) { + var fallback = document.createElement('script'); + fallback.src = cdnSrc; + fallback.onload = function() { captureImage(); }; + fallback.onerror = function() { + alert('Failed to load image export library. Please try using browser screenshot or print-to-PDF instead.'); + }; + document.head.appendChild(fallback); + } else { + alert('Failed to load image export library. Please try using browser screenshot or print-to-PDF instead.'); + } + }; + document.head.appendChild(script); + } else { + captureImage(); + } + } + + function captureImage() { + var container = document.getElementById('saturationreport-container'); + if (!container) { + container = document.getElementById('report'); + } + if (!container) { + alert('Report container not found'); + return; + } + + var btn = $('#export-image-btn'); + var originalText = btn.html(); + btn.prop('disabled', true).html(' Generating...'); + + html2canvas(container, { + backgroundColor: '#ffffff', + scale: 2, + logging: false, + useCORS: true + }).then(function(canvas) { + var link = document.createElement('a'); + link.download = 'saturation-report-' + new Date().getTime() + '.png'; + link.href = canvas.toDataURL('image/png'); + link.click(); + btn.prop('disabled', false).html(originalText); + }).catch(function(err) { + console.error('Error capturing image:', err); + alert('Failed to generate image. Please try using browser screenshot or print-to-PDF instead.'); + btn.prop('disabled', false).html(originalText); + }); + } +});