766 lines
26 KiB
Python
766 lines
26 KiB
Python
#!/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 = """
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Vendor Report Generator</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
|
overflow: hidden;
|
|
}
|
|
|
|
header {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
padding: 30px;
|
|
text-align: center;
|
|
}
|
|
|
|
header h1 {
|
|
font-size: 2.5em;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
header p {
|
|
opacity: 0.9;
|
|
font-size: 1.1em;
|
|
}
|
|
|
|
.content {
|
|
padding: 40px;
|
|
}
|
|
|
|
.section {
|
|
margin-bottom: 40px;
|
|
padding: 30px;
|
|
background: #f9fafb;
|
|
border-radius: 8px;
|
|
border: 1px solid #e5e7eb;
|
|
}
|
|
|
|
.section h2 {
|
|
color: #1e40af;
|
|
margin-bottom: 20px;
|
|
font-size: 1.5em;
|
|
}
|
|
|
|
.button-group {
|
|
display: flex;
|
|
gap: 15px;
|
|
flex-wrap: wrap;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 12px 24px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 1em;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #2563eb;
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: #1d4ed8;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);
|
|
}
|
|
|
|
.btn-success {
|
|
background: #10b981;
|
|
color: white;
|
|
}
|
|
|
|
.btn-success:hover {
|
|
background: #059669;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #6b7280;
|
|
color: white;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: #4b5563;
|
|
}
|
|
|
|
.btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.status-card {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
border-left: 4px solid #2563eb;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.status-card h3 {
|
|
color: #374151;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.status-card p {
|
|
color: #6b7280;
|
|
margin: 5px 0;
|
|
}
|
|
|
|
.status-indicator {
|
|
display: inline-block;
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.status-indicator.active {
|
|
background: #10b981;
|
|
}
|
|
|
|
.status-indicator.inactive {
|
|
background: #ef4444;
|
|
}
|
|
|
|
.loading {
|
|
display: none;
|
|
text-align: center;
|
|
padding: 20px;
|
|
}
|
|
|
|
.loading.active {
|
|
display: block;
|
|
}
|
|
|
|
.spinner {
|
|
border: 4px solid #f3f4f6;
|
|
border-top: 4px solid #2563eb;
|
|
border-radius: 50%;
|
|
width: 40px;
|
|
height: 40px;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 15px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
.alert {
|
|
padding: 15px;
|
|
border-radius: 6px;
|
|
margin-bottom: 20px;
|
|
display: none;
|
|
}
|
|
|
|
.alert.active {
|
|
display: block;
|
|
}
|
|
|
|
.alert-success {
|
|
background: #d1fae5;
|
|
color: #065f46;
|
|
border: 1px solid #10b981;
|
|
}
|
|
|
|
.alert-error {
|
|
background: #fee2e2;
|
|
color: #991b1b;
|
|
border: 1px solid #ef4444;
|
|
}
|
|
|
|
.alert-info {
|
|
background: #dbeafe;
|
|
color: #1e40af;
|
|
border: 1px solid #2563eb;
|
|
}
|
|
|
|
.report-list {
|
|
list-style: none;
|
|
}
|
|
|
|
.report-item {
|
|
background: white;
|
|
padding: 15px;
|
|
border-radius: 6px;
|
|
margin-bottom: 10px;
|
|
border: 1px solid #e5e7eb;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.report-item:hover {
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.report-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.report-info strong {
|
|
color: #1e40af;
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.report-info small {
|
|
color: #6b7280;
|
|
}
|
|
|
|
.config-item {
|
|
margin-bottom: 15px;
|
|
padding: 15px;
|
|
background: white;
|
|
border-radius: 6px;
|
|
border: 1px solid #e5e7eb;
|
|
}
|
|
|
|
.config-item label {
|
|
display: block;
|
|
font-weight: 600;
|
|
color: #374151;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.config-item .value {
|
|
color: #6b7280;
|
|
font-family: monospace;
|
|
}
|
|
|
|
.badge {
|
|
display: inline-block;
|
|
padding: 4px 12px;
|
|
border-radius: 12px;
|
|
font-size: 0.85em;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.badge-enabled {
|
|
background: #d1fae5;
|
|
color: #065f46;
|
|
}
|
|
|
|
.badge-disabled {
|
|
background: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header>
|
|
<h1>📊 Vendor Report Generator</h1>
|
|
<p>Generate comprehensive vendor punchlist reports from Excel files</p>
|
|
</header>
|
|
|
|
<div class="content">
|
|
<div id="alert-container"></div>
|
|
|
|
<!-- Update Data Section -->
|
|
<div class="section">
|
|
<h2>Update Data</h2>
|
|
<p>Download the latest Excel files from SharePoint to update your local data.</p>
|
|
|
|
<div class="button-group">
|
|
<button class="btn btn-success" onclick="updateFromSharePoint()">
|
|
Update Data from SharePoint
|
|
</button>
|
|
</div>
|
|
|
|
<div class="loading" id="loading-update">
|
|
<div class="spinner"></div>
|
|
<p>Downloading files from SharePoint... This may take a moment.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Generate Report Section -->
|
|
<div class="section">
|
|
<h2>Generate Report</h2>
|
|
<p>Generate a new report from Excel files in the local reports directory.</p>
|
|
|
|
<div class="button-group">
|
|
<button class="btn btn-primary" onclick="generateReport()">
|
|
Generate Report
|
|
</button>
|
|
</div>
|
|
|
|
<div class="loading" id="loading">
|
|
<div class="spinner"></div>
|
|
<p>Generating report... This may take a moment.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Section -->
|
|
<div class="section">
|
|
<h2>Service Status</h2>
|
|
<div id="status-container">
|
|
<div class="status-card">
|
|
<h3>Loading status...</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reports Section -->
|
|
<div class="section">
|
|
<h2>Generated Reports</h2>
|
|
<div id="reports-container">
|
|
<p>Loading reports...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Configuration Section -->
|
|
<div class="section">
|
|
<h2>Configuration</h2>
|
|
<div id="config-container">
|
|
<p>Loading configuration...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Update data from SharePoint
|
|
async function updateFromSharePoint() {
|
|
const loading = document.getElementById('loading-update');
|
|
const alertContainer = document.getElementById('alert-container');
|
|
|
|
loading.classList.add('active');
|
|
alertContainer.innerHTML = '';
|
|
|
|
try {
|
|
const response = await fetch('/api/update-sharepoint', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
showAlert('success', `Successfully downloaded ${data.downloaded_count} file(s) from SharePoint!`);
|
|
loadStatus();
|
|
} else {
|
|
showAlert('error', `Error: ${data.error || 'Failed to download from SharePoint'}`);
|
|
}
|
|
} catch (error) {
|
|
showAlert('error', `Error: ${error.message}`);
|
|
} finally {
|
|
loading.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
// Generate report
|
|
async function generateReport() {
|
|
const loading = document.getElementById('loading');
|
|
const alertContainer = document.getElementById('alert-container');
|
|
|
|
loading.classList.add('active');
|
|
alertContainer.innerHTML = '';
|
|
|
|
try {
|
|
const response = await fetch('/api/generate', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
download_from_sharepoint: false
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
showAlert('success', `Report generated successfully! Processed ${data.vendors_count || 0} vendors.`);
|
|
loadReports();
|
|
loadStatus();
|
|
} else {
|
|
showAlert('error', `Error: ${data.error || 'Failed to generate report'}`);
|
|
}
|
|
} catch (error) {
|
|
showAlert('error', `Error: ${error.message}`);
|
|
console.error('Generate report error:', error);
|
|
} finally {
|
|
loading.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
// Load status
|
|
async function loadStatus() {
|
|
try {
|
|
const response = await fetch('/api/status');
|
|
const data = await response.json();
|
|
|
|
const container = document.getElementById('status-container');
|
|
container.innerHTML = `
|
|
<div class="status-card">
|
|
<h3>
|
|
<span class="status-indicator ${data.status === 'running' ? 'active' : 'inactive'}"></span>
|
|
Service Status: ${data.status}
|
|
</h3>
|
|
<p><strong>SharePoint:</strong> <span class="badge ${data.sharepoint_enabled ? 'badge-enabled' : 'badge-disabled'}">${data.sharepoint_enabled ? 'Enabled' : 'Disabled'}</span></p>
|
|
<p><strong>Reports Directory:</strong> ${data.reports_dir}</p>
|
|
<p><strong>Output Directory:</strong> ${data.output_dir}</p>
|
|
</div>
|
|
`;
|
|
} catch (error) {
|
|
console.error('Failed to load status:', error);
|
|
}
|
|
}
|
|
|
|
// Load reports
|
|
async function loadReports() {
|
|
try {
|
|
const response = await fetch('/api/reports');
|
|
const data = await response.json();
|
|
|
|
const container = document.getElementById('reports-container');
|
|
|
|
if (data.reports && data.reports.length > 0) {
|
|
const reportsList = data.reports.map(report => `
|
|
<div class="report-item">
|
|
<div class="report-info">
|
|
<strong>${report.name}</strong>
|
|
<small>Generated: ${report.generated_at} | Size: ${report.size}</small>
|
|
</div>
|
|
<div>
|
|
<a href="/reports/${report.name}" class="btn btn-primary" target="_blank">View HTML</a>
|
|
${report.json_exists ? `<a href="/reports/${report.json_name}" class="btn btn-secondary" download>Download JSON</a>` : ''}
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
container.innerHTML = `<ul class="report-list">${reportsList}</ul>`;
|
|
} else {
|
|
container.innerHTML = '<p>No reports generated yet.</p>';
|
|
} catch (error) {
|
|
console.error('Failed to load reports:', error);
|
|
document.getElementById('reports-container').innerHTML = '<p>Error loading reports.</p>';
|
|
}
|
|
}
|
|
|
|
// Load configuration
|
|
async function loadConfig() {
|
|
try {
|
|
const response = await fetch('/api/config');
|
|
const config = await response.json();
|
|
|
|
const container = document.getElementById('config-container');
|
|
const configItems = Object.entries(config).map(([key, value]) => {
|
|
const displayValue = typeof value === 'boolean'
|
|
? `<span class="badge ${value ? 'badge-enabled' : 'badge-disabled'}">${value ? 'Enabled' : 'Disabled'}</span>`
|
|
: String(value || 'Not configured');
|
|
return `
|
|
<div class="config-item">
|
|
<label>${key.replace(/_/g, ' ').replace(/\\b\\w/g, l => l.toUpperCase())}</label>
|
|
<div class="value">${displayValue}</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
container.innerHTML = configItems;
|
|
} catch (error) {
|
|
console.error('Failed to load config:', error);
|
|
document.getElementById('config-container').innerHTML = '<p>Error loading configuration.</p>';
|
|
}
|
|
}
|
|
|
|
// Show alert
|
|
function showAlert(type, message) {
|
|
const container = document.getElementById('alert-container');
|
|
const alert = document.createElement('div');
|
|
alert.className = `alert alert-${type} active`;
|
|
alert.textContent = message;
|
|
container.appendChild(alert);
|
|
|
|
setTimeout(() => {
|
|
alert.remove();
|
|
}, 5000);
|
|
}
|
|
|
|
// Load data on page load
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
loadStatus();
|
|
loadReports();
|
|
loadConfig();
|
|
|
|
// Refresh every 30 seconds
|
|
setInterval(() => {
|
|
loadStatus();
|
|
loadReports();
|
|
}, 30000);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
|
|
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/<filename>')
|
|
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)
|
|
|