#!/usr/bin/env python3 """ Web UI for Vendor Report Generator Provides a simple web interface for generating reports, viewing status, and managing configuration. """ import logging import json from pathlib import Path from typing import Optional from datetime import datetime try: from flask import Flask, render_template_string, jsonify, request, send_from_directory, redirect, url_for from flask_cors import CORS FLASK_AVAILABLE = True except ImportError: FLASK_AVAILABLE = False logging.warning("Flask not installed. Web UI features disabled.") from config import load_config from report_generator import generate_report from sharepoint_downloader import download_from_sharepoint logger = logging.getLogger(__name__) app = None config = None # HTML Template for the Web UI UI_TEMPLATE = """ Vendor Report Generator

📊 Vendor Report Generator

Generate comprehensive vendor punchlist reports from Excel files

Update Data

Download the latest Excel files from SharePoint to update your local data.

Downloading files from SharePoint... This may take a moment.

Generate Report

Generate a new report from Excel files in the local reports directory.

Generating report... This may take a moment.

Service Status

Loading status...

Generated Reports

Loading reports...

Configuration

Loading configuration...

""" def create_app(config_path: Optional[str] = None): """Create and configure Flask app with Web UI.""" global app, config if not FLASK_AVAILABLE: raise ImportError( "Flask is required for Web UI. " "Install it with: pip install flask flask-cors" ) app = Flask(__name__) CORS(app) config = load_config(config_path) api_config = config.get('api', {}) sharepoint_config = config.get('sharepoint', {}) report_config = config.get('report', {}) app.config['API_KEY'] = api_config.get('api_key') app.config['SHAREPOINT_CONFIG'] = sharepoint_config app.config['REPORT_CONFIG'] = report_config @app.route('/') def index(): """Main web UI page.""" return render_template_string(UI_TEMPLATE) @app.route('/api/update-sharepoint', methods=['POST']) def update_sharepoint_endpoint(): """Download files from SharePoint.""" api_key = app.config.get('API_KEY') if api_key: provided_key = request.headers.get('X-API-Key') or (request.json.get('api_key') if request.json else None) if provided_key != api_key: return jsonify({'error': 'Invalid API key'}), 401 try: sp_config = app.config['SHAREPOINT_CONFIG'] if not sp_config.get('enabled'): return jsonify({'error': 'SharePoint is not enabled in configuration'}), 400 logger.info("Downloading files from SharePoint...") try: downloaded = download_from_sharepoint( site_url=sp_config['site_url'], folder_path=sp_config.get('folder_path'), file_path=sp_config.get('file_path'), local_dir=sp_config.get('local_dir', 'reports'), tenant_id=sp_config.get('tenant_id'), client_id=sp_config.get('client_id'), client_secret=sp_config.get('client_secret'), use_app_authentication=sp_config.get('use_app_authentication', True), file_pattern=sp_config.get('file_pattern'), overwrite=sp_config.get('overwrite', True) ) logger.info(f"Downloaded {len(downloaded)} file(s) from SharePoint") return jsonify({ 'status': 'success', 'message': f'Successfully downloaded {len(downloaded)} file(s) from SharePoint', 'downloaded_count': len(downloaded), 'files': downloaded }) except Exception as e: logger.error(f"Failed to download from SharePoint: {e}", exc_info=True) return jsonify({'error': f'SharePoint download failed: {str(e)}'}), 500 except Exception as e: logger.error(f"Error updating from SharePoint: {e}", exc_info=True) return jsonify({'error': f'Update failed: {str(e)}'}), 500 @app.route('/api/generate', methods=['POST']) def generate_report_endpoint(): """Generate report on demand.""" api_key = app.config.get('API_KEY') if api_key: provided_key = request.headers.get('X-API-Key') or (request.json.get('api_key') if request.json else None) if provided_key != api_key: return jsonify({'error': 'Invalid API key'}), 401 try: request_data = request.json or {} report_config = app.config['REPORT_CONFIG'] reports_dir = request_data.get('reports_dir', report_config.get('reports_dir', 'reports')) output_file = request_data.get('output_file', str(Path(report_config.get('output_dir', 'output')) / 'report.json')) # Check if reports directory exists and has files reports_path = Path(reports_dir) if not reports_path.exists(): return jsonify({'error': f'Reports directory not found: {reports_dir}'}), 400 excel_files = list(reports_path.glob('*.xlsx')) + list(reports_path.glob('*.xls')) if not excel_files: return jsonify({'error': f'No Excel files found in {reports_dir}. Please update data from SharePoint first.'}), 400 logger.info(f"Generating report from {reports_dir} ({len(excel_files)} Excel file(s))...") report_data = generate_report( reports_dir=reports_dir, output_file=output_file, verbose=False ) if report_data and report_data.get('vendors'): return jsonify({ 'status': 'success', 'message': 'Report generated successfully', 'output_file': output_file, 'summary': report_data.get('summary', {}), 'vendors_count': len(report_data.get('vendors', [])) }) else: return jsonify({'error': 'Report generation failed - no data processed'}), 500 except Exception as e: logger.error(f"Error generating report: {e}", exc_info=True) return jsonify({'error': f'Report generation failed: {str(e)}'}), 500 @app.route('/api/status', methods=['GET']) def status(): """Get service status.""" return jsonify({ 'status': 'running', 'sharepoint_enabled': app.config['SHAREPOINT_CONFIG'].get('enabled', False), 'reports_dir': app.config['REPORT_CONFIG'].get('reports_dir', 'reports'), 'output_dir': app.config['REPORT_CONFIG'].get('output_dir', 'output') }) @app.route('/api/reports', methods=['GET']) def list_reports(): """List generated reports.""" output_dir = Path(app.config['REPORT_CONFIG'].get('output_dir', 'output')) reports = [] if output_dir.exists(): html_files = list(output_dir.glob('*.html')) for html_file in html_files: json_file = html_file.with_suffix('.json') reports.append({ 'name': html_file.name, 'json_name': json_file.name if json_file.exists() else None, 'json_exists': json_file.exists(), 'generated_at': datetime.fromtimestamp(html_file.stat().st_mtime).strftime('%Y-%m-%d %H:%M:%S'), 'size': f"{html_file.stat().st_size / 1024:.1f} KB" }) # Sort by modification time (newest first) reports.sort(key=lambda x: x['generated_at'], reverse=True) return jsonify({'reports': reports}) @app.route('/api/config', methods=['GET']) def get_config(): """Get configuration (safe, no secrets).""" return jsonify({ 'sharepoint_enabled': app.config['SHAREPOINT_CONFIG'].get('enabled', False), 'sharepoint_site_url': app.config['SHAREPOINT_CONFIG'].get('site_url', 'Not configured'), 'sharepoint_folder_path': app.config['SHAREPOINT_CONFIG'].get('folder_path', 'Not configured'), 'reports_dir': app.config['REPORT_CONFIG'].get('reports_dir', 'reports'), 'output_dir': app.config['REPORT_CONFIG'].get('output_dir', 'output') }) @app.route('/reports/') def serve_report(filename): """Serve report files.""" output_dir = Path(app.config['REPORT_CONFIG'].get('output_dir', 'output')) return send_from_directory(str(output_dir), filename) @app.route('/health', methods=['GET']) def health(): """Health check.""" return jsonify({'status': 'healthy', 'service': 'vendor-report-generator-ui'}) return app def run_server(config_path: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None): """Run the Web UI server.""" app = create_app(config_path) api_config = config.get('api', {}) server_host = host or api_config.get('host', '0.0.0.0') server_port = port or api_config.get('port', 8080) logger.info(f"Starting Web UI server on http://{server_host}:{server_port}") app.run(host=server_host, port=server_port, debug=False) if __name__ == "__main__": import sys logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) config_path = sys.argv[1] if len(sys.argv) > 1 else None config = load_config(config_path) if not config.get('api', {}).get('enabled', False): logger.warning("API is disabled in configuration, but starting Web UI anyway...") run_server(config_path=config_path)