diff --git a/src/invoice.py b/src/invoice.py index dbde63f..271c3d6 100755 --- a/src/invoice.py +++ b/src/invoice.py @@ -13,12 +13,23 @@ import sys from datetime import datetime -from reportlab.lib import colors -from reportlab.lib.enums import TA_RIGHT -from reportlab.lib.pagesizes import LETTER -from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet -from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, - TableStyle) +# Reportlab is required for PDF generation. If it's missing, emit a clear +# error message on stderr so callers can surface the failure to users. +try: + from reportlab.lib import colors + from reportlab.lib.enums import TA_RIGHT + from reportlab.lib.pagesizes import LETTER + from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet + from reportlab.platypus import ( + SimpleDocTemplate, + Paragraph, + Spacer, + Table, + TableStyle, + ) +except ModuleNotFoundError as exc: # pragma: no cover - exercised via JS + print(str(exc), file=sys.stderr) + sys.exit(1) def _load_profile(base_dir): @@ -174,8 +185,10 @@ def main(): buffer = io.BytesIO() generate_invoice(buffer, invoice_data) - pdf_bytes = buffer.getvalue() + buffer.seek(0) + pdf_bytes = buffer.read() sys.stdout.write(base64.b64encode(pdf_bytes).decode("ascii")) + sys.stdout.flush() if __name__ == "__main__": diff --git a/src/slurmcostmanager.js b/src/slurmcostmanager.js index eef896a..b48035c 100644 --- a/src/slurmcostmanager.js +++ b/src/slurmcostmanager.js @@ -905,11 +905,26 @@ function Details({ }; try { setError(null); + if (!filteredDetails.length) { + setError('No usage data matches current filters'); + return; + } const output = await window.cockpit.spawn( ['python3', `${PLUGIN_BASE}/invoice.py`], { input: JSON.stringify(invoiceData), err: 'out' } ); - const byteChars = atob(output.trim()); + const trimmed = output.trim(); + if (!trimmed) { + setError('Invoice generation returned no data'); + return; + } + let byteChars; + try { + byteChars = atob(trimmed); + } catch (decodeErr) { + setError(trimmed || decodeErr.message || String(decodeErr)); + return; + } const byteNumbers = new Array(byteChars.length); for (let i = 0; i < byteChars.length; i++) { byteNumbers[i] = byteChars.charCodeAt(i);