Upload files to "scripts/vendor"

This commit is contained in:
2026-02-10 18:54:17 +00:00
parent e422cbfcda
commit 22dd77ee61
2 changed files with 478 additions and 0 deletions

302
scripts/vendor/export_report.py vendored Normal file
View File

@@ -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'''
<tr>
<td colspan="5" style="text-align: center; padding: 20px; color: #d9534f; font-size: 14px;">
<strong>Error:</strong> {services['error']}
</td>
</tr>
'''
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'''
<div style="width: 100%; background-color: #f0f0f0; border-radius: 4px; overflow: hidden;">
<div style="width: {disk_percent}%; background-color: {'#d9534f' if disk_percent >= 95 else '#f0ad4e' if disk_percent >= 85 else '#5bc0de' if disk_percent >= 70 else '#5cb85c'};
height: 20px; text-align: center; line-height: 20px; color: white; font-size: 11px; font-weight: bold;">
{disk_percent_display}
</div>
</div>
'''
else:
progress_bar = '<span>N/A</span>'
# 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'''
<tr>
<td>{host_name}</td>
<td>{caption}</td>
<td>{disk_used}</td>
<td style="color: {available_color};">{disk_available}</td>
<td>{progress_bar}</td>
</tr>
'''
else:
table_rows = '''
<tr>
<td colspan="5" style="text-align: center; padding: 20px; color: #999;">
No data available
</td>
</tr>
'''
html_template = f'''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{title}</title>
<style>
@page {{
size: letter;
margin: 0.75in;
}}
body {{
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background: #fff;
color: #333;
}}
.report-header {{
margin-bottom: 30px;
border-bottom: 2px solid #2c3e50;
padding-bottom: 15px;
}}
.report-header h1 {{
margin: 0 0 10px 0;
font-size: 24px;
font-weight: bold;
color: #2c3e50;
}}
.report-header .subtitle {{
font-size: 14px;
color: #666;
margin: 5px 0;
}}
.report-header .report-date {{
font-size: 12px;
color: #999;
margin: 5px 0;
}}
.report-header .report-order {{
font-size: 12px;
color: #999;
margin: 5px 0;
}}
table {{
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}}
th {{
background-color: #2c3e50;
color: #ecf0f1;
font-weight: bold;
text-align: left;
padding: 12px 15px;
border: 1px solid #34495e;
text-transform: uppercase;
font-size: 12px;
letter-spacing: 0.5px;
}}
td {{
padding: 10px 15px;
border: 1px solid #e0e0e0;
font-size: 13px;
}}
tr:nth-child(even) {{
background-color: #f9f9f9;
}}
tr:last-child td {{
border-bottom: none;
}}
.text-danger {{
color: #d9534f;
}}
.text-warning {{
color: #f0ad4e;
}}
.text-success {{
color: #5cb85c;
}}
.text-muted {{
color: #999;
}}
</style>
</head>
<body>
<div class="report-header">
<h1>{title}</h1>
<p class="subtitle">Summary of Tracked Storage: Volumes</p>
<p class="report-date">Report Date: {date}</p>
<p class="report-order">Ordered by: Disk Space Available - Ascending</p>
</div>
<table>
<thead>
<tr>
<th>DISPLAY NAME</th>
<th>CAPTION</th>
<th>DISK SPACE USED</th>
<th>DISK SPACE AVAILABLE</th>
<th>PERCENT USED</th>
</tr>
</thead>
<tbody>
{table_rows}
</tbody>
</table>
</body>
</html>
'''
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()

176
scripts/vendor/main.js vendored Normal file
View File

@@ -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(
'<div class="alert alert-danger">' +
'Error loading storage saturation report.' +
'</div>'
);
});
}
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('<i class="fa fa-spinner fa-spin"></i> 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('<i class="fa fa-spinner fa-spin"></i> 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);
});
}
});