updated control access
This commit is contained in:
@@ -1,361 +0,0 @@
|
||||
# Quality Recticel Windows Print Service - Installation Guide
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
The Quality Recticel Windows Print Service enables **silent PDF printing** directly from the web application through a Chrome extension. This system eliminates the need for manual PDF downloads and provides seamless label printing functionality.
|
||||
|
||||
## 🏗️ System Architecture
|
||||
|
||||
```
|
||||
Web Application (print_module.html)
|
||||
↓
|
||||
Windows Print Service (localhost:8765)
|
||||
↓
|
||||
Chrome Extension (Native Messaging)
|
||||
↓
|
||||
Windows Print System
|
||||
```
|
||||
|
||||
## 📦 Package Contents
|
||||
|
||||
```
|
||||
windows_print_service/
|
||||
├── print_service.py # Main Windows service (Flask API)
|
||||
├── service_manager.py # Service installation & management
|
||||
├── install_service.bat # Automated installation script
|
||||
├── chrome_extension/ # Chrome extension files
|
||||
│ ├── manifest.json # Extension configuration
|
||||
│ ├── background.js # Service worker
|
||||
│ ├── content.js # Page integration
|
||||
│ ├── popup.html # Extension UI
|
||||
│ ├── popup.js # Extension logic
|
||||
│ └── icons/ # Extension icons
|
||||
└── INSTALLATION_GUIDE.md # This documentation
|
||||
```
|
||||
|
||||
## 🔧 Prerequisites
|
||||
|
||||
### System Requirements
|
||||
- **Operating System**: Windows 10/11 (64-bit)
|
||||
- **Python**: Python 3.8 or higher
|
||||
- **Browser**: Google Chrome (latest version)
|
||||
- **Privileges**: Administrator access required for installation
|
||||
|
||||
### Python Dependencies
|
||||
The following packages will be installed automatically:
|
||||
- `flask` - Web service framework
|
||||
- `flask-cors` - Cross-origin resource sharing
|
||||
- `requests` - HTTP client library
|
||||
- `pywin32` - Windows service integration
|
||||
|
||||
## 🚀 Installation Process
|
||||
|
||||
### Step 1: Download and Extract Files
|
||||
|
||||
1. Download the `windows_print_service` folder to your system
|
||||
2. Extract to a permanent location (e.g., `C:\QualityRecticel\PrintService\`)
|
||||
3. **Do not move or delete this folder after installation**
|
||||
|
||||
### Step 2: Install Windows Service
|
||||
|
||||
#### Method A: Automated Installation (Recommended)
|
||||
|
||||
1. **Right-click** on `install_service.bat`
|
||||
2. Select **"Run as administrator"**
|
||||
3. Click **"Yes"** when Windows UAC prompt appears
|
||||
4. Wait for installation to complete
|
||||
|
||||
#### Method B: Manual Installation
|
||||
|
||||
If the automated script fails, follow these steps:
|
||||
|
||||
```bash
|
||||
# Open Command Prompt as Administrator
|
||||
cd C:\path\to\windows_print_service
|
||||
|
||||
# Install Python dependencies
|
||||
pip install flask flask-cors requests pywin32
|
||||
|
||||
# Install Windows service
|
||||
python service_manager.py install
|
||||
|
||||
# Add firewall exception
|
||||
netsh advfirewall firewall add rule name="Quality Recticel Print Service" dir=in action=allow protocol=TCP localport=8765
|
||||
|
||||
# Create Chrome extension registry entry
|
||||
reg add "HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.qualityrecticel.printservice" /ve /d "%cd%\chrome_extension\manifest.json" /f
|
||||
```
|
||||
|
||||
### Step 3: Install Chrome Extension
|
||||
|
||||
1. Open **Google Chrome**
|
||||
2. Navigate to `chrome://extensions/`
|
||||
3. Enable **"Developer mode"** (toggle in top-right corner)
|
||||
4. Click **"Load unpacked"**
|
||||
5. Select the `chrome_extension` folder
|
||||
6. Verify the extension appears with a printer icon
|
||||
|
||||
### Step 4: Verify Installation
|
||||
|
||||
#### Check Windows Service Status
|
||||
|
||||
1. Press `Win + R`, type `services.msc`, press Enter
|
||||
2. Look for **"Quality Recticel Print Service"**
|
||||
3. Status should show **"Running"**
|
||||
4. Startup type should be **"Automatic"**
|
||||
|
||||
#### Test API Endpoints
|
||||
|
||||
Open a web browser and visit:
|
||||
- **Health Check**: `http://localhost:8765/health`
|
||||
- **Printer List**: `http://localhost:8765/printers`
|
||||
|
||||
Expected response for health check:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"service": "Quality Recticel Print Service",
|
||||
"version": "1.0",
|
||||
"timestamp": "2025-09-21T10:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
#### Test Chrome Extension
|
||||
|
||||
1. Click the extension icon in Chrome toolbar
|
||||
2. Verify it shows "Service Status: Connected ✅"
|
||||
3. Check that printers are listed
|
||||
4. Try the "Test Print" button
|
||||
|
||||
## 🔄 Web Application Integration
|
||||
|
||||
The web application automatically detects the Windows service and adapts the user interface:
|
||||
|
||||
### Service Available (Green Button)
|
||||
- Button text: **"🖨️ Print Labels (Silent)"**
|
||||
- Functionality: Direct printing to default printer
|
||||
- User experience: Click → Labels print immediately
|
||||
|
||||
### Service Unavailable (Blue Button)
|
||||
- Button text: **"📄 Generate PDF"**
|
||||
- Functionality: PDF download for manual printing
|
||||
- User experience: Click → PDF downloads to browser
|
||||
|
||||
### Detection Logic
|
||||
```javascript
|
||||
// Automatic service detection on page load
|
||||
const response = await fetch('http://localhost:8765/health');
|
||||
if (response.ok) {
|
||||
// Service available - enable silent printing
|
||||
} else {
|
||||
// Service unavailable - fallback to PDF download
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ Configuration
|
||||
|
||||
### Service Configuration
|
||||
|
||||
The service runs with the following default settings:
|
||||
|
||||
| Setting | Value | Description |
|
||||
|---------|-------|-------------|
|
||||
| **Port** | 8765 | Local API port |
|
||||
| **Host** | localhost | Service binding |
|
||||
| **Startup** | Automatic | Starts with Windows |
|
||||
| **Printer** | Default | Uses system default printer |
|
||||
| **Copies** | 1 | Default print copies |
|
||||
|
||||
### Chrome Extension Permissions
|
||||
|
||||
The extension requires these permissions:
|
||||
- `printing` - Access to printer functionality
|
||||
- `nativeMessaging` - Communication with Windows service
|
||||
- `activeTab` - Access to current webpage
|
||||
- `storage` - Save extension settings
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### 1. Service Not Starting
|
||||
**Symptoms**: API not accessible at localhost:8765
|
||||
**Solutions**:
|
||||
```bash
|
||||
# Check service status
|
||||
python -c "from service_manager import service_status; service_status()"
|
||||
|
||||
# Restart service manually
|
||||
python service_manager.py restart
|
||||
|
||||
# Check Windows Event Viewer for service errors
|
||||
```
|
||||
|
||||
#### 2. Chrome Extension Not Working
|
||||
**Symptoms**: Extension shows "Service Status: Disconnected ❌"
|
||||
**Solutions**:
|
||||
- Verify Windows service is running
|
||||
- Check firewall settings (port 8765 must be open)
|
||||
- Reload the Chrome extension
|
||||
- Restart Chrome browser
|
||||
|
||||
#### 3. Firewall Blocking Connection
|
||||
**Symptoms**: Service runs but web page can't connect
|
||||
**Solutions**:
|
||||
```bash
|
||||
# Add firewall rule manually
|
||||
netsh advfirewall firewall add rule name="Quality Recticel Print Service" dir=in action=allow protocol=TCP localport=8765
|
||||
|
||||
# Or disable Windows Firewall temporarily to test
|
||||
```
|
||||
|
||||
#### 4. Permission Denied Errors
|
||||
**Symptoms**: Installation fails with permission errors
|
||||
**Solutions**:
|
||||
- Ensure running as Administrator
|
||||
- Check Windows UAC settings
|
||||
- Verify Python installation permissions
|
||||
|
||||
#### 5. Print Jobs Not Processing
|
||||
**Symptoms**: API accepts requests but nothing prints
|
||||
**Solutions**:
|
||||
- Check default printer configuration
|
||||
- Verify printer drivers are installed
|
||||
- Test manual printing from other applications
|
||||
- Check Windows Print Spooler service
|
||||
|
||||
### Log Files
|
||||
|
||||
Check these locations for troubleshooting:
|
||||
|
||||
| Component | Log Location |
|
||||
|-----------|--------------|
|
||||
| **Windows Service** | `print_service.log` (same folder as service) |
|
||||
| **Chrome Extension** | Chrome DevTools → Extensions → Background page |
|
||||
| **Windows Event Log** | Event Viewer → Windows Logs → System |
|
||||
|
||||
### Diagnostic Commands
|
||||
|
||||
```bash
|
||||
# Check service status
|
||||
python service_manager.py status
|
||||
|
||||
# Test API manually
|
||||
curl http://localhost:8765/health
|
||||
|
||||
# List available printers
|
||||
curl http://localhost:8765/printers
|
||||
|
||||
# Check Windows service
|
||||
sc query QualityRecticelPrintService
|
||||
|
||||
# Check listening ports
|
||||
netstat -an | findstr :8765
|
||||
```
|
||||
|
||||
## 🔄 Maintenance
|
||||
|
||||
### Updating the Service
|
||||
|
||||
1. Stop the current service:
|
||||
```bash
|
||||
python service_manager.py stop
|
||||
```
|
||||
|
||||
2. Replace service files with new versions
|
||||
|
||||
3. Restart the service:
|
||||
```bash
|
||||
python service_manager.py start
|
||||
```
|
||||
|
||||
### Uninstalling
|
||||
|
||||
#### Remove Chrome Extension
|
||||
1. Go to `chrome://extensions/`
|
||||
2. Find "Quality Recticel Print Service"
|
||||
3. Click "Remove"
|
||||
|
||||
#### Remove Windows Service
|
||||
```bash
|
||||
# Run as Administrator
|
||||
python service_manager.py uninstall
|
||||
```
|
||||
|
||||
#### Remove Firewall Rule
|
||||
```bash
|
||||
netsh advfirewall firewall delete rule name="Quality Recticel Print Service"
|
||||
```
|
||||
|
||||
## 📞 Support Information
|
||||
|
||||
### API Endpoints Reference
|
||||
|
||||
| Endpoint | Method | Purpose |
|
||||
|----------|--------|---------|
|
||||
| `/health` | GET | Service health check |
|
||||
| `/printers` | GET | List available printers |
|
||||
| `/print/pdf` | POST | Print PDF from URL |
|
||||
| `/print/silent` | POST | Silent print with metadata |
|
||||
|
||||
### Request Examples
|
||||
|
||||
**Silent Print Request**:
|
||||
```json
|
||||
POST /print/silent
|
||||
{
|
||||
"pdf_url": "http://localhost:5000/generate_labels_pdf/123",
|
||||
"printer_name": "default",
|
||||
"copies": 1,
|
||||
"silent": true,
|
||||
"order_id": "123",
|
||||
"quantity": "10"
|
||||
}
|
||||
```
|
||||
|
||||
**Expected Response**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Print job sent successfully",
|
||||
"job_id": "print_20250921_103000",
|
||||
"printer": "HP LaserJet Pro",
|
||||
"timestamp": "2025-09-21T10:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 Technical Details
|
||||
|
||||
### Service Architecture
|
||||
- **Framework**: Flask (Python)
|
||||
- **Service Type**: Windows Service (pywin32)
|
||||
- **Communication**: HTTP REST API + Native Messaging
|
||||
- **Security**: Localhost binding only (127.0.0.1:8765)
|
||||
|
||||
### Chrome Extension Architecture
|
||||
- **Manifest Version**: 3
|
||||
- **Service Worker**: Handles background print requests
|
||||
- **Content Script**: Integrates with Quality Recticel web pages
|
||||
- **Native Messaging**: Communicates with Windows service
|
||||
|
||||
### Security Considerations
|
||||
- Service only accepts local connections (localhost)
|
||||
- No external network access required
|
||||
- Chrome extension runs in sandboxed environment
|
||||
- Windows service runs with system privileges (required for printing)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Quick Start Checklist
|
||||
|
||||
- [ ] Download `windows_print_service` folder
|
||||
- [ ] Right-click `install_service.bat` → "Run as administrator"
|
||||
- [ ] Install Chrome extension from `chrome_extension` folder
|
||||
- [ ] Verify service at `http://localhost:8765/health`
|
||||
- [ ] Test printing from Quality Recticel web application
|
||||
|
||||
**Installation Time**: ~5 minutes
|
||||
**User Training Required**: Minimal (automatic detection and fallback)
|
||||
**Maintenance**: Zero (auto-starts with Windows)
|
||||
|
||||
For additional support, check the log files and diagnostic commands listed above.
|
||||
@@ -1,69 +0,0 @@
|
||||
# 🚀 Quality Recticel Print Service - Quick Setup
|
||||
|
||||
## 📦 What You Get
|
||||
- **Silent PDF Printing** - No more manual downloads!
|
||||
- **Automatic Detection** - Smart fallback when service unavailable
|
||||
- **Zero Configuration** - Works out of the box
|
||||
|
||||
## ⚡ 2-Minute Installation
|
||||
|
||||
### Step 1: Install Windows Service
|
||||
1. **Right-click** `install_service.bat`
|
||||
2. Select **"Run as administrator"**
|
||||
3. Click **"Yes"** and wait for completion
|
||||
|
||||
### Step 2: Install Chrome Extension
|
||||
1. Open Chrome → `chrome://extensions/`
|
||||
2. Enable **"Developer mode"**
|
||||
3. Click **"Load unpacked"** → Select `chrome_extension` folder
|
||||
|
||||
### Step 3: Verify Installation
|
||||
- Visit: `http://localhost:8765/health`
|
||||
- Should see: `{"status": "healthy"}`
|
||||
|
||||
## 🎯 How It Works
|
||||
|
||||
| Service Status | Button Appearance | What Happens |
|
||||
|---------------|-------------------|--------------|
|
||||
| **Running** ✅ | 🖨️ **Print Labels (Silent)** (Green) | Direct printing |
|
||||
| **Not Running** ❌ | 📄 **Generate PDF** (Blue) | PDF download |
|
||||
|
||||
## ⚠️ Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| **Service won't start** | Run `install_service.bat` as Administrator |
|
||||
| **Chrome extension not working** | Reload extension in `chrome://extensions/` |
|
||||
| **Can't connect to localhost:8765** | Check Windows Firewall (port 8765) |
|
||||
| **Nothing prints** | Verify default printer is set up |
|
||||
|
||||
## 🔧 Management Commands
|
||||
|
||||
```bash
|
||||
# Check service status
|
||||
python service_manager.py status
|
||||
|
||||
# Restart service
|
||||
python service_manager.py restart
|
||||
|
||||
# Uninstall service
|
||||
python service_manager.py uninstall
|
||||
```
|
||||
|
||||
## 📍 Important Notes
|
||||
|
||||
- ⚡ **Auto-starts** with Windows - no manual intervention needed
|
||||
- 🔒 **Local only** - service only accessible from same computer
|
||||
- 🖨️ **Uses default printer** - configure your default printer in Windows
|
||||
- 💾 **Don't move files** after installation - keep folder in same location
|
||||
|
||||
## 🆘 Quick Support
|
||||
|
||||
**Service API**: `http://localhost:8765`
|
||||
**Health Check**: `http://localhost:8765/health`
|
||||
**Printer List**: `http://localhost:8765/printers`
|
||||
|
||||
**Log File**: `print_service.log` (same folder as installation)
|
||||
|
||||
---
|
||||
*Installation takes ~5 minutes • Zero maintenance required • Works with existing Quality Recticel web application*
|
||||
@@ -1,348 +0,0 @@
|
||||
# Quality Recticel Windows Print Service
|
||||
|
||||
## 🏗️ Technical Architecture
|
||||
|
||||
Local Windows service providing REST API for silent PDF printing via Chrome extension integration.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Quality Recticel Web App │
|
||||
│ (print_module.html) │
|
||||
└─────────────────────┬───────────────────────────────────────┘
|
||||
│ HTTP Request
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Windows Print Service │
|
||||
│ (localhost:8765) │
|
||||
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
|
||||
│ │ Flask │ │ CORS │ │ PDF Handler │ │
|
||||
│ │ Server │ │ Support │ │ │ │
|
||||
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
|
||||
└─────────────────────┬───────────────────────────────────────┘
|
||||
│ Native Messaging
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Chrome Extension │
|
||||
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
|
||||
│ │ Background │ │ Content │ │ Popup │ │
|
||||
│ │ Service │ │ Script │ │ UI │ │
|
||||
│ │ Worker │ │ │ │ │ │
|
||||
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
|
||||
└─────────────────────┬───────────────────────────────────────┘
|
||||
│ Windows API
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Windows Print System │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
windows_print_service/
|
||||
├── 📄 print_service.py # Main Flask service
|
||||
├── 📄 service_manager.py # Windows service wrapper
|
||||
├── 📄 install_service.bat # Installation script
|
||||
├── 📄 INSTALLATION_GUIDE.md # Complete documentation
|
||||
├── 📄 QUICK_SETUP.md # User quick reference
|
||||
├── 📄 README.md # This file
|
||||
└── 📁 chrome_extension/ # Chrome extension
|
||||
├── 📄 manifest.json # Extension manifest v3
|
||||
├── 📄 background.js # Service worker
|
||||
├── 📄 content.js # Page content integration
|
||||
├── 📄 popup.html # Extension popup UI
|
||||
├── 📄 popup.js # Popup functionality
|
||||
└── 📁 icons/ # Extension icons
|
||||
```
|
||||
|
||||
## 🚀 API Endpoints
|
||||
|
||||
### Base URL: `http://localhost:8765`
|
||||
|
||||
| Endpoint | Method | Description | Request Body | Response |
|
||||
|----------|--------|-------------|--------------|----------|
|
||||
| `/health` | GET | Service health check | None | `{"status": "healthy", ...}` |
|
||||
| `/printers` | GET | List available printers | None | `{"printers": [...]}` |
|
||||
| `/print/pdf` | POST | Print PDF from URL | `{"url": "...", "printer": "..."}` | `{"success": true, ...}` |
|
||||
| `/print/silent` | POST | Silent print with metadata | `{"pdf_url": "...", "order_id": "..."}` | `{"success": true, ...}` |
|
||||
|
||||
### Example API Usage
|
||||
|
||||
```javascript
|
||||
// Health Check
|
||||
const health = await fetch('http://localhost:8765/health');
|
||||
const status = await health.json();
|
||||
|
||||
// Silent Print
|
||||
const printRequest = {
|
||||
pdf_url: 'http://localhost:5000/generate_labels_pdf/123',
|
||||
printer_name: 'default',
|
||||
copies: 1,
|
||||
silent: true,
|
||||
order_id: '123',
|
||||
quantity: '10'
|
||||
};
|
||||
|
||||
const response = await fetch('http://localhost:8765/print/silent', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(printRequest)
|
||||
});
|
||||
```
|
||||
|
||||
## 🔧 Development Setup
|
||||
|
||||
### Prerequisites
|
||||
- Python 3.8+
|
||||
- Windows 10/11
|
||||
- Chrome Browser
|
||||
- Administrator privileges
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# Clone/download the project
|
||||
cd windows_print_service
|
||||
|
||||
# Install dependencies
|
||||
pip install flask flask-cors requests pywin32
|
||||
|
||||
# Run development server (not as service)
|
||||
python print_service.py
|
||||
|
||||
# Install as Windows service
|
||||
python service_manager.py install
|
||||
|
||||
# Service management
|
||||
python service_manager.py start
|
||||
python service_manager.py stop
|
||||
python service_manager.py restart
|
||||
python service_manager.py uninstall
|
||||
```
|
||||
|
||||
### Chrome Extension Development
|
||||
|
||||
```bash
|
||||
# Load extension in Chrome
|
||||
chrome://extensions/ → Developer mode ON → Load unpacked
|
||||
|
||||
# Debug extension
|
||||
chrome://extensions/ → Details → Background page (for service worker)
|
||||
chrome://extensions/ → Details → Inspect views (for popup)
|
||||
```
|
||||
|
||||
## 📋 Configuration
|
||||
|
||||
### Service Configuration (`print_service.py`)
|
||||
|
||||
```python
|
||||
class WindowsPrintService:
|
||||
def __init__(self, host='127.0.0.1', port=8765):
|
||||
self.host = host # Localhost binding only
|
||||
self.port = port # Service port
|
||||
self.app = Flask(__name__)
|
||||
```
|
||||
|
||||
### Chrome Extension Permissions (`manifest.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"permissions": [
|
||||
"printing", // Access to printer API
|
||||
"nativeMessaging", // Communication with Windows service
|
||||
"activeTab", // Current tab access
|
||||
"storage" // Extension settings storage
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 🔄 Integration Flow
|
||||
|
||||
### 1. Service Detection
|
||||
```javascript
|
||||
// Web page detects service availability
|
||||
const isServiceAvailable = await checkServiceHealth();
|
||||
updatePrintButton(isServiceAvailable);
|
||||
```
|
||||
|
||||
### 2. Print Request Flow
|
||||
```
|
||||
User clicks print → Web app → Windows service → Chrome extension → Printer
|
||||
```
|
||||
|
||||
### 3. Fallback Mechanism
|
||||
```
|
||||
Service unavailable → Fallback to PDF download → Manual printing
|
||||
```
|
||||
|
||||
## 🛠️ Customization
|
||||
|
||||
### Adding New Print Options
|
||||
|
||||
```python
|
||||
# In print_service.py
|
||||
@app.route('/print/custom', methods=['POST'])
|
||||
def print_custom():
|
||||
data = request.json
|
||||
# Custom print logic here
|
||||
return jsonify({'success': True})
|
||||
```
|
||||
|
||||
### Modifying Chrome Extension
|
||||
|
||||
```javascript
|
||||
// In background.js - Add new message handler
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.type === 'CUSTOM_PRINT') {
|
||||
// Custom print logic
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Web Application Integration
|
||||
|
||||
```javascript
|
||||
// In print_module.html - Modify print function
|
||||
async function customPrintFunction(orderId) {
|
||||
const response = await fetch('http://localhost:8765/print/custom', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({orderId, customOptions: {...}})
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Unit Tests (Future Enhancement)
|
||||
|
||||
```python
|
||||
# test_print_service.py
|
||||
import unittest
|
||||
from print_service import WindowsPrintService
|
||||
|
||||
class TestPrintService(unittest.TestCase):
|
||||
def test_health_endpoint(self):
|
||||
# Test implementation
|
||||
pass
|
||||
```
|
||||
|
||||
### Manual Testing Checklist
|
||||
|
||||
- [ ] Service starts automatically on Windows boot
|
||||
- [ ] API endpoints respond correctly
|
||||
- [ ] Chrome extension loads without errors
|
||||
- [ ] Print jobs execute successfully
|
||||
- [ ] Fallback works when service unavailable
|
||||
- [ ] Firewall allows port 8765 traffic
|
||||
|
||||
## 📊 Monitoring & Logging
|
||||
|
||||
### Log Files
|
||||
- **Service Log**: `print_service.log` (Flask application logs)
|
||||
- **Windows Event Log**: Windows Services logs
|
||||
- **Chrome DevTools**: Extension console logs
|
||||
|
||||
### Health Monitoring
|
||||
|
||||
```python
|
||||
# Monitor service health
|
||||
import requests
|
||||
try:
|
||||
response = requests.get('http://localhost:8765/health', timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("✅ Service healthy")
|
||||
except:
|
||||
print("❌ Service unavailable")
|
||||
```
|
||||
|
||||
## 🔒 Security Considerations
|
||||
|
||||
### Network Security
|
||||
- **Localhost Only**: Service binds to 127.0.0.1 (no external access)
|
||||
- **No Authentication**: Relies on local machine security
|
||||
- **Firewall Rule**: Port 8765 opened for local connections only
|
||||
|
||||
### Chrome Extension Security
|
||||
- **Manifest V3**: Latest security standards
|
||||
- **Minimal Permissions**: Only necessary permissions requested
|
||||
- **Sandboxed**: Runs in Chrome's security sandbox
|
||||
|
||||
### Windows Service Security
|
||||
- **System Service**: Runs with appropriate Windows service privileges
|
||||
- **Print Permissions**: Requires printer access (normal for print services)
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
### Production Deployment
|
||||
|
||||
1. **Package Distribution**:
|
||||
```bash
|
||||
# Create deployment package
|
||||
zip -r quality_recticel_print_service.zip windows_print_service/
|
||||
```
|
||||
|
||||
2. **Installation Script**: Use `install_service.bat` for end users
|
||||
|
||||
3. **Group Policy Deployment**: Deploy Chrome extension via enterprise policies
|
||||
|
||||
### Enterprise Considerations
|
||||
|
||||
- **Silent Installation**: Modify `install_service.bat` for unattended install
|
||||
- **Registry Deployment**: Pre-configure Chrome extension registry entries
|
||||
- **Network Policies**: Ensure firewall policies allow localhost:8765
|
||||
|
||||
## 📚 Dependencies
|
||||
|
||||
### Python Packages
|
||||
```
|
||||
flask>=2.3.0 # Web framework
|
||||
flask-cors>=4.0.0 # CORS support
|
||||
requests>=2.31.0 # HTTP client
|
||||
pywin32>=306 # Windows service integration
|
||||
```
|
||||
|
||||
### Chrome APIs
|
||||
- `chrome.printing.*` - Printing functionality
|
||||
- `chrome.runtime.*` - Extension messaging
|
||||
- `chrome.nativeMessaging.*` - Native app communication
|
||||
|
||||
## 🐛 Debugging
|
||||
|
||||
### Common Debug Commands
|
||||
|
||||
```bash
|
||||
# Check service status
|
||||
sc query QualityRecticelPrintService
|
||||
|
||||
# Test API manually
|
||||
curl http://localhost:8765/health
|
||||
|
||||
# Check listening ports
|
||||
netstat -an | findstr :8765
|
||||
|
||||
# View service logs
|
||||
type print_service.log
|
||||
```
|
||||
|
||||
### Chrome Extension Debugging
|
||||
|
||||
```javascript
|
||||
// In background.js - Add debug logging
|
||||
console.log('Print request received:', message);
|
||||
|
||||
// In popup.js - Test API connection
|
||||
fetch('http://localhost:8765/health')
|
||||
.then(r => r.json())
|
||||
.then(data => console.log('Service status:', data));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 License & Support
|
||||
|
||||
**Project**: Quality Recticel Print Service
|
||||
**Version**: 1.0
|
||||
**Compatibility**: Windows 10/11, Chrome 88+
|
||||
**Maintenance**: Zero-maintenance after installation
|
||||
|
||||
For technical support, refer to `INSTALLATION_GUIDE.md` troubleshooting section.
|
||||
539
py_app/app/static/fg_quality.js
Normal file
539
py_app/app/static/fg_quality.js
Normal file
@@ -0,0 +1,539 @@
|
||||
// FG Quality specific JavaScript - Standalone version
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Prevent conflicts with main script.js by removing existing listeners
|
||||
console.log('FG Quality JavaScript loaded');
|
||||
|
||||
const reportButtons = document.querySelectorAll('.report-btn');
|
||||
const reportTable = document.getElementById('report-table');
|
||||
const reportTitle = document.getElementById('report-title');
|
||||
const exportCsvButton = document.getElementById('export-csv');
|
||||
|
||||
// Calendar elements
|
||||
const calendarModal = document.getElementById('calendar-modal');
|
||||
const dateRangeModal = document.getElementById('date-range-modal');
|
||||
const selectDayReport = document.getElementById('select-day-report');
|
||||
const selectDayDefectsReport = document.getElementById('select-day-defects-report');
|
||||
const dateRangeReport = document.getElementById('date-range-report');
|
||||
const dateRangeDefectsReport = document.getElementById('date-range-defects-report');
|
||||
|
||||
let currentReportType = null;
|
||||
let currentDate = new Date();
|
||||
let selectedDate = null;
|
||||
|
||||
// Clear any existing event listeners by cloning elements
|
||||
function clearExistingListeners() {
|
||||
if (selectDayReport) {
|
||||
const newSelectDayReport = selectDayReport.cloneNode(true);
|
||||
selectDayReport.parentNode.replaceChild(newSelectDayReport, selectDayReport);
|
||||
}
|
||||
if (selectDayDefectsReport) {
|
||||
const newSelectDayDefectsReport = selectDayDefectsReport.cloneNode(true);
|
||||
selectDayDefectsReport.parentNode.replaceChild(newSelectDayDefectsReport, selectDayDefectsReport);
|
||||
}
|
||||
if (dateRangeReport) {
|
||||
const newDateRangeReport = dateRangeReport.cloneNode(true);
|
||||
dateRangeReport.parentNode.replaceChild(newDateRangeReport, dateRangeReport);
|
||||
}
|
||||
if (dateRangeDefectsReport) {
|
||||
const newDateRangeDefectsReport = dateRangeDefectsReport.cloneNode(true);
|
||||
dateRangeDefectsReport.parentNode.replaceChild(newDateRangeDefectsReport, dateRangeDefectsReport);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear existing listeners first
|
||||
clearExistingListeners();
|
||||
|
||||
// Re-get elements after cloning
|
||||
const newSelectDayReport = document.getElementById('select-day-report');
|
||||
const newSelectDayDefectsReport = document.getElementById('select-day-defects-report');
|
||||
const newDateRangeReport = document.getElementById('date-range-report');
|
||||
const newDateRangeDefectsReport = document.getElementById('date-range-defects-report');
|
||||
|
||||
// Add event listeners to report buttons
|
||||
reportButtons.forEach(button => {
|
||||
const reportType = button.getAttribute('data-report');
|
||||
if (reportType) {
|
||||
// Clone to remove existing listeners
|
||||
const newButton = button.cloneNode(true);
|
||||
button.parentNode.replaceChild(newButton, button);
|
||||
|
||||
newButton.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Report button clicked:', reportType);
|
||||
fetchFGReportData(reportType);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calendar-based report buttons with FG-specific handlers
|
||||
if (newSelectDayReport) {
|
||||
newSelectDayReport.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Select Day Report clicked');
|
||||
currentReportType = '6';
|
||||
showCalendarModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (newSelectDayDefectsReport) {
|
||||
newSelectDayDefectsReport.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Select Day Defects Report clicked');
|
||||
currentReportType = '8';
|
||||
showCalendarModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (newDateRangeReport) {
|
||||
newDateRangeReport.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Date Range Report clicked');
|
||||
currentReportType = '7';
|
||||
showDateRangeModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (newDateRangeDefectsReport) {
|
||||
newDateRangeDefectsReport.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Date Range Defects Report clicked');
|
||||
currentReportType = '9';
|
||||
showDateRangeModal();
|
||||
});
|
||||
}
|
||||
|
||||
// Function to fetch FG report data
|
||||
function fetchFGReportData(reportType) {
|
||||
const url = `/get_fg_report_data?report=${reportType}`;
|
||||
console.log('Fetching FG data from:', url);
|
||||
reportTitle.textContent = 'Loading FG data...';
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('FG Report data received:', data);
|
||||
if (data.error) {
|
||||
reportTitle.textContent = data.error;
|
||||
return;
|
||||
}
|
||||
|
||||
populateFGTable(data);
|
||||
updateReportTitle(reportType);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching FG report data:', error);
|
||||
reportTitle.textContent = 'Error loading FG data.';
|
||||
});
|
||||
}
|
||||
|
||||
// Function to fetch FG report data for specific dates
|
||||
function fetchFGDateReportData(reportType, date, startDate = null, endDate = null) {
|
||||
let url = `/generate_fg_report?report=${reportType}`;
|
||||
if (date) {
|
||||
url += `&date=${date}`;
|
||||
}
|
||||
if (startDate && endDate) {
|
||||
url += `&start_date=${startDate}&end_date=${endDate}`;
|
||||
}
|
||||
|
||||
console.log('Fetching FG date report from:', url);
|
||||
reportTitle.textContent = 'Loading FG data...';
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('FG Date report data received:', data);
|
||||
if (data.error) {
|
||||
reportTitle.textContent = data.error;
|
||||
return;
|
||||
}
|
||||
|
||||
populateFGTable(data);
|
||||
updateDateReportTitle(reportType, date, startDate, endDate);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching FG date report data:', error);
|
||||
reportTitle.textContent = 'Error loading FG data.';
|
||||
});
|
||||
}
|
||||
|
||||
// Function to populate the table with FG data
|
||||
function populateFGTable(data) {
|
||||
const thead = reportTable.querySelector('thead tr');
|
||||
const tbody = reportTable.querySelector('tbody');
|
||||
|
||||
// Clear existing content
|
||||
thead.innerHTML = '';
|
||||
tbody.innerHTML = '';
|
||||
|
||||
// Add headers
|
||||
if (data.headers && data.headers.length > 0) {
|
||||
data.headers.forEach(header => {
|
||||
const th = document.createElement('th');
|
||||
th.textContent = header;
|
||||
thead.appendChild(th);
|
||||
});
|
||||
}
|
||||
|
||||
// Add rows
|
||||
if (data.rows && data.rows.length > 0) {
|
||||
data.rows.forEach(row => {
|
||||
const tr = document.createElement('tr');
|
||||
row.forEach(cell => {
|
||||
const td = document.createElement('td');
|
||||
td.textContent = cell || '';
|
||||
tr.appendChild(td);
|
||||
});
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
} else {
|
||||
// Show no data message
|
||||
const tr = document.createElement('tr');
|
||||
const td = document.createElement('td');
|
||||
td.colSpan = data.headers ? data.headers.length : 1;
|
||||
td.textContent = data.message || 'No FG data found for the selected criteria.';
|
||||
td.style.textAlign = 'center';
|
||||
td.style.fontStyle = 'italic';
|
||||
td.style.padding = '20px';
|
||||
tr.appendChild(td);
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update report title based on type
|
||||
function updateReportTitle(reportType) {
|
||||
const titles = {
|
||||
'1': 'Daily Complete FG Orders Report',
|
||||
'2': '5-Day Complete FG Orders Report',
|
||||
'3': 'FG Items with Defects for Current Day',
|
||||
'4': 'FG Items with Defects for Last 5 Days',
|
||||
'5': 'Complete FG Database Report'
|
||||
};
|
||||
|
||||
reportTitle.textContent = titles[reportType] || 'FG Quality Report';
|
||||
}
|
||||
|
||||
// Function to update report title for date-based reports
|
||||
function updateDateReportTitle(reportType, date, startDate, endDate) {
|
||||
const titles = {
|
||||
'6': `FG Daily Report for ${date}`,
|
||||
'7': `FG Date Range Report (${startDate} to ${endDate})`,
|
||||
'8': `FG Quality Defects Report for ${date}`,
|
||||
'9': `FG Quality Defects Range Report (${startDate} to ${endDate})`
|
||||
};
|
||||
|
||||
reportTitle.textContent = titles[reportType] || 'FG Quality Report';
|
||||
}
|
||||
|
||||
// Calendar functionality
|
||||
function showCalendarModal() {
|
||||
if (calendarModal) {
|
||||
calendarModal.style.display = 'block';
|
||||
generateCalendar();
|
||||
}
|
||||
}
|
||||
|
||||
function hideCalendarModal() {
|
||||
if (calendarModal) {
|
||||
calendarModal.style.display = 'none';
|
||||
selectedDate = null;
|
||||
updateConfirmButton();
|
||||
}
|
||||
}
|
||||
|
||||
function showDateRangeModal() {
|
||||
if (dateRangeModal) {
|
||||
dateRangeModal.style.display = 'block';
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('start-date').value = today;
|
||||
document.getElementById('end-date').value = today;
|
||||
}
|
||||
}
|
||||
|
||||
function hideDataRangeModal() {
|
||||
if (dateRangeModal) {
|
||||
dateRangeModal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function generateCalendar() {
|
||||
const calendarDays = document.getElementById('calendar-days');
|
||||
const monthYear = document.getElementById('calendar-month-year');
|
||||
|
||||
if (!calendarDays || !monthYear) return;
|
||||
|
||||
const year = currentDate.getFullYear();
|
||||
const month = currentDate.getMonth();
|
||||
|
||||
monthYear.textContent = `${currentDate.toLocaleString('default', { month: 'long' })} ${year}`;
|
||||
|
||||
// Clear previous days
|
||||
calendarDays.innerHTML = '';
|
||||
|
||||
// Get first day of month and number of days
|
||||
const firstDay = new Date(year, month, 1).getDay();
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
|
||||
// Add empty cells for previous month
|
||||
for (let i = 0; i < firstDay; i++) {
|
||||
const emptyDay = document.createElement('div');
|
||||
emptyDay.className = 'calendar-day empty';
|
||||
calendarDays.appendChild(emptyDay);
|
||||
}
|
||||
|
||||
// Add days of current month
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const dayElement = document.createElement('div');
|
||||
dayElement.className = 'calendar-day';
|
||||
dayElement.textContent = day;
|
||||
|
||||
// Check if it's today
|
||||
const today = new Date();
|
||||
if (year === today.getFullYear() && month === today.getMonth() && day === today.getDate()) {
|
||||
dayElement.classList.add('today');
|
||||
}
|
||||
|
||||
dayElement.addEventListener('click', () => {
|
||||
// Remove previous selection
|
||||
document.querySelectorAll('.calendar-day.selected').forEach(el => {
|
||||
el.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Add selection to clicked day
|
||||
dayElement.classList.add('selected');
|
||||
|
||||
// Set selected date
|
||||
selectedDate = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
console.log('FG Calendar date selected:', selectedDate);
|
||||
updateConfirmButton();
|
||||
});
|
||||
|
||||
calendarDays.appendChild(dayElement);
|
||||
}
|
||||
}
|
||||
|
||||
function updateConfirmButton() {
|
||||
const confirmButton = document.getElementById('confirm-date');
|
||||
if (confirmButton) {
|
||||
confirmButton.disabled = !selectedDate;
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar navigation
|
||||
const prevMonthBtn = document.getElementById('prev-month');
|
||||
const nextMonthBtn = document.getElementById('next-month');
|
||||
|
||||
if (prevMonthBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newPrevBtn = prevMonthBtn.cloneNode(true);
|
||||
prevMonthBtn.parentNode.replaceChild(newPrevBtn, prevMonthBtn);
|
||||
|
||||
newPrevBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
currentDate.setMonth(currentDate.getMonth() - 1);
|
||||
generateCalendar();
|
||||
});
|
||||
}
|
||||
|
||||
if (nextMonthBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newNextBtn = nextMonthBtn.cloneNode(true);
|
||||
nextMonthBtn.parentNode.replaceChild(newNextBtn, nextMonthBtn);
|
||||
|
||||
newNextBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
currentDate.setMonth(currentDate.getMonth() + 1);
|
||||
generateCalendar();
|
||||
});
|
||||
}
|
||||
|
||||
// Calendar modal buttons
|
||||
const cancelDateBtn = document.getElementById('cancel-date');
|
||||
const confirmDateBtn = document.getElementById('confirm-date');
|
||||
|
||||
if (cancelDateBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newCancelBtn = cancelDateBtn.cloneNode(true);
|
||||
cancelDateBtn.parentNode.replaceChild(newCancelBtn, cancelDateBtn);
|
||||
|
||||
newCancelBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
hideCalendarModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (confirmDateBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newConfirmBtn = confirmDateBtn.cloneNode(true);
|
||||
confirmDateBtn.parentNode.replaceChild(newConfirmBtn, confirmDateBtn);
|
||||
|
||||
newConfirmBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Calendar confirm clicked with date:', selectedDate, 'report type:', currentReportType);
|
||||
if (selectedDate && currentReportType) {
|
||||
fetchFGDateReportData(currentReportType, selectedDate);
|
||||
hideCalendarModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Date range modal buttons
|
||||
const cancelDateRangeBtn = document.getElementById('cancel-date-range');
|
||||
const confirmDateRangeBtn = document.getElementById('confirm-date-range');
|
||||
|
||||
if (cancelDateRangeBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newCancelRangeBtn = cancelDateRangeBtn.cloneNode(true);
|
||||
cancelDateRangeBtn.parentNode.replaceChild(newCancelRangeBtn, cancelDateRangeBtn);
|
||||
|
||||
newCancelRangeBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
hideDataRangeModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (confirmDateRangeBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newConfirmRangeBtn = confirmDateRangeBtn.cloneNode(true);
|
||||
confirmDateRangeBtn.parentNode.replaceChild(newConfirmRangeBtn, confirmDateRangeBtn);
|
||||
|
||||
newConfirmRangeBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const startDate = document.getElementById('start-date').value;
|
||||
const endDate = document.getElementById('end-date').value;
|
||||
|
||||
console.log('FG Date range confirm clicked:', startDate, 'to', endDate, 'report type:', currentReportType);
|
||||
if (startDate && endDate && currentReportType) {
|
||||
fetchFGDateReportData(currentReportType, null, startDate, endDate);
|
||||
hideDataRangeModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Enable/disable date range confirm button
|
||||
const startDateInput = document.getElementById('start-date');
|
||||
const endDateInput = document.getElementById('end-date');
|
||||
|
||||
function updateDateRangeConfirmButton() {
|
||||
const confirmBtn = document.getElementById('confirm-date-range');
|
||||
if (confirmBtn && startDateInput && endDateInput) {
|
||||
confirmBtn.disabled = !startDateInput.value || !endDateInput.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (startDateInput) {
|
||||
startDateInput.addEventListener('change', updateDateRangeConfirmButton);
|
||||
}
|
||||
|
||||
if (endDateInput) {
|
||||
endDateInput.addEventListener('change', updateDateRangeConfirmButton);
|
||||
}
|
||||
|
||||
// Close modals when clicking outside
|
||||
window.addEventListener('click', (event) => {
|
||||
if (event.target === calendarModal) {
|
||||
hideCalendarModal();
|
||||
}
|
||||
if (event.target === dateRangeModal) {
|
||||
hideDataRangeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Close modals with X button
|
||||
document.querySelectorAll('.close-modal').forEach(closeBtn => {
|
||||
// Clone to remove existing listeners
|
||||
const newCloseBtn = closeBtn.cloneNode(true);
|
||||
closeBtn.parentNode.replaceChild(newCloseBtn, closeBtn);
|
||||
|
||||
newCloseBtn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const modal = event.target.closest('.modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Export functionality
|
||||
if (exportCsvButton) {
|
||||
exportCsvButton.addEventListener('click', () => {
|
||||
const rows = reportTable.querySelectorAll('tr');
|
||||
if (rows.length === 0) {
|
||||
alert('No FG data available to export.');
|
||||
return;
|
||||
}
|
||||
const reportTitleText = reportTitle.textContent.trim();
|
||||
const filename = `${reportTitleText.replace(/\s+/g, '_')}.csv`;
|
||||
exportTableToCSV(filename);
|
||||
});
|
||||
}
|
||||
|
||||
// Export to CSV function
|
||||
function exportTableToCSV(filename) {
|
||||
const table = reportTable;
|
||||
const rows = Array.from(table.querySelectorAll('tr'));
|
||||
|
||||
const csvContent = rows.map(row => {
|
||||
const cells = Array.from(row.querySelectorAll('th, td'));
|
||||
return cells.map(cell => {
|
||||
let text = cell.textContent.trim();
|
||||
// Escape quotes and wrap in quotes if necessary
|
||||
if (text.includes(',') || text.includes('"') || text.includes('\n')) {
|
||||
text = '"' + text.replace(/"/g, '""') + '"';
|
||||
}
|
||||
return text;
|
||||
}).join(',');
|
||||
}).join('\n');
|
||||
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', filename);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
// Test Database Button
|
||||
const testDatabaseBtn = document.getElementById('test-database');
|
||||
if (testDatabaseBtn) {
|
||||
testDatabaseBtn.addEventListener('click', () => {
|
||||
console.log('Testing FG database connection...');
|
||||
reportTitle.textContent = 'Testing FG Database Connection...';
|
||||
fetch('/test_fg_database')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('FG Database test results:', data);
|
||||
if (data.success) {
|
||||
reportTitle.textContent = `FG Database Test Results - ${data.total_records} records found`;
|
||||
// Show alert with summary
|
||||
alert(`FG Database Test Complete!\n\nConnection: ${data.database_connection}\nTable exists: ${data.table_exists}\nTotal records: ${data.total_records}\nMessage: ${data.message}`);
|
||||
} else {
|
||||
reportTitle.textContent = 'FG Database Test Failed';
|
||||
alert(`FG Database test failed: ${data.message}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('FG Database test error:', error);
|
||||
reportTitle.textContent = 'Error testing FG database.';
|
||||
alert('Error testing FG database connection.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
console.log('FG Quality JavaScript setup complete');
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
QZ TRAY LIBRARY PATCH NOTES
|
||||
===========================
|
||||
Version: 2.2.4 (patched for custom QZ Tray with pairing key authentication)
|
||||
Date: October 2, 2025
|
||||
|
||||
CHANGES MADE:
|
||||
-------------
|
||||
|
||||
1. Line ~387: Commented out certificate sending
|
||||
- Original: _qz.websocket.connection.sendData({ certificate: cert, promise: openPromise });
|
||||
- Patched: openPromise.resolve(); (resolves immediately without sending certificate)
|
||||
|
||||
2. Line ~391-403: Bypassed certificate retrieval
|
||||
- Original: Called _qz.security.callCert() to get certificate from user
|
||||
- Patched: Directly calls sendCert(null) without trying to get certificate
|
||||
|
||||
3. Comments added to indicate patches
|
||||
|
||||
REASON FOR PATCHES:
|
||||
------------------
|
||||
The custom QZ Tray server has certificate validation COMPLETELY DISABLED.
|
||||
It uses ONLY pairing key (HMAC) authentication instead of certificates.
|
||||
The original qz-tray.js library expects certificate-based authentication and
|
||||
fails when the server doesn't respond to certificate requests.
|
||||
|
||||
COMPATIBILITY:
|
||||
-------------
|
||||
- Works with custom QZ Tray server (forked version with certificate validation disabled)
|
||||
- NOT compatible with standard QZ Tray servers
|
||||
- Connects to both ws://localhost:8181 and wss://localhost:8182
|
||||
- Authentication handled by server-side pairing keys
|
||||
|
||||
BACKUP:
|
||||
-------
|
||||
Original unpatched version saved as: qz-tray.js.backup
|
||||
Reference in New Issue
Block a user