Pre-github
This commit is contained in:
@@ -59,3 +59,12 @@ htmlcov/
|
|||||||
# Production
|
# Production
|
||||||
*.pid
|
*.pid
|
||||||
*.sock
|
*.sock
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
information/deprecated/
|
||||||
|
planning/
|
||||||
|
*.7z
|
||||||
|
*.zip
|
||||||
|
.htaccess_fixed
|
||||||
|
teacher_retention_trend.svg
|
||||||
|
wordpress_.htaccess
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Apache configuration for Flask subdirectory deployment
|
|
||||||
# Passenger configuration ONLY - minimal to avoid blocking issues
|
|
||||||
|
|
||||||
PassengerEnabled on
|
|
||||||
PassengerAppRoot /home/bmdwtjuw/product-finder
|
|
||||||
PassengerPython /home/bmdwtjuw/virtualenv/product-finder/3.13/bin/python3.13_bin
|
|
||||||
SetEnv APPLICATION_ROOT /product-finder
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# MINIMAL .htaccess for troubleshooting
|
|
||||||
# If you're getting 403/404, try this simplified version first
|
|
||||||
|
|
||||||
PassengerEnabled on
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
# Admin Utilities
|
|
||||||
|
|
||||||
This folder contains administrative tools and utilities for maintaining the CGW Product Finder application.
|
|
||||||
|
|
||||||
## 🛠️ Available Tools
|
|
||||||
|
|
||||||
### fix_default_locations.py
|
|
||||||
**Purpose**: Ensures all users have their default location marked as accessible.
|
|
||||||
|
|
||||||
**Usage**:
|
|
||||||
```bash
|
|
||||||
cd app/admin
|
|
||||||
python fix_default_locations.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**What it does**:
|
|
||||||
- Scans all users in `data/users.json`
|
|
||||||
- Checks if each user's default location is marked as accessible
|
|
||||||
- Automatically fixes any users where the default location is not accessible
|
|
||||||
- Reports results for each user
|
|
||||||
|
|
||||||
**When to use**:
|
|
||||||
- After manual edits to users.json
|
|
||||||
- After importing users from backup
|
|
||||||
- If users report they can't access their default location
|
|
||||||
- As a maintenance check
|
|
||||||
|
|
||||||
### test_password_security.py
|
|
||||||
**Purpose**: Demonstrates and tests the password hashing security implementation.
|
|
||||||
|
|
||||||
**Usage**:
|
|
||||||
```bash
|
|
||||||
cd app/admin
|
|
||||||
python test_password_security.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**What it does**:
|
|
||||||
- Demonstrates PBKDF2-SHA256 password hashing
|
|
||||||
- Shows security features (iterations, salt, etc.)
|
|
||||||
- Verifies password verification works correctly
|
|
||||||
- Useful for understanding the security implementation
|
|
||||||
|
|
||||||
**When to use**:
|
|
||||||
- To verify password hashing is working correctly
|
|
||||||
- To demonstrate security to stakeholders
|
|
||||||
- For educational purposes
|
|
||||||
- When troubleshooting password-related issues
|
|
||||||
|
|
||||||
## 📋 Running Admin Tools
|
|
||||||
|
|
||||||
All admin tools should be run from within the `app/admin` directory to ensure correct file paths.
|
|
||||||
|
|
||||||
**Example**:
|
|
||||||
```bash
|
|
||||||
# Navigate to admin folder
|
|
||||||
cd "C:\Users\Work\Desktop\CGW Product Finder\app\admin"
|
|
||||||
|
|
||||||
# Run a tool
|
|
||||||
python fix_default_locations.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## ⚠️ Important Notes
|
|
||||||
|
|
||||||
- **Backup First**: Always backup `data/users.json` before running utilities that modify data
|
|
||||||
- **Test Environment**: Test utilities in a development environment before running in production
|
|
||||||
- **File Paths**: These tools assume they're being run from the `app/admin` directory
|
|
||||||
- **Python Version**: Requires Python 3.7 or higher
|
|
||||||
|
|
||||||
## 🔒 Security Considerations
|
|
||||||
|
|
||||||
- These tools have direct access to user data
|
|
||||||
- Do not expose these tools to web-facing directories
|
|
||||||
- Keep this folder secure with appropriate file permissions
|
|
||||||
- Never commit sensitive user data to version control
|
|
||||||
|
|
||||||
## 📝 Adding New Admin Tools
|
|
||||||
|
|
||||||
When adding new administrative tools to this folder:
|
|
||||||
|
|
||||||
1. Add the Python script file
|
|
||||||
2. Update this README with documentation
|
|
||||||
3. Include clear usage instructions
|
|
||||||
4. Document any data modifications it makes
|
|
||||||
5. Include error handling and user feedback
|
|
||||||
|
|
||||||
## 🐛 Troubleshooting
|
|
||||||
|
|
||||||
**"File not found" errors**:
|
|
||||||
- Ensure you're running from the `app/admin` directory
|
|
||||||
- Check that `../data/users.json` exists
|
|
||||||
|
|
||||||
**Permission denied**:
|
|
||||||
- Ensure the application is not running
|
|
||||||
- Check file permissions on `data/users.json`
|
|
||||||
|
|
||||||
**Import errors**:
|
|
||||||
- Ensure all required dependencies are installed: `pip install -r ../requirements.txt`
|
|
||||||
- Verify Python version: `python --version`
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
"""
|
|
||||||
Utility script to fix existing users by ensuring their default location
|
|
||||||
is marked as accessible in their locationSettings.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Get the directory where this script is located
|
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
USERS_FILE = os.path.join(basedir, '..', 'data', 'users.json')
|
|
||||||
|
|
||||||
def fix_default_locations():
|
|
||||||
"""
|
|
||||||
Ensure all users have their default location marked as accessible.
|
|
||||||
"""
|
|
||||||
if not os.path.exists(USERS_FILE):
|
|
||||||
print("No users.json file found.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Load users
|
|
||||||
with open(USERS_FILE, 'r') as f:
|
|
||||||
users = json.load(f)
|
|
||||||
|
|
||||||
print(f"Found {len(users)} users to check...")
|
|
||||||
|
|
||||||
changes_made = 0
|
|
||||||
for user in users:
|
|
||||||
username = user.get('username', 'Unknown')
|
|
||||||
default_location = user.get('defaultLocation')
|
|
||||||
|
|
||||||
if not default_location:
|
|
||||||
print(f"⚠️ User '{username}' has no default location - skipping")
|
|
||||||
continue
|
|
||||||
|
|
||||||
location_settings = user.get('locationSettings', {})
|
|
||||||
|
|
||||||
# Check if default location is in settings and accessible
|
|
||||||
if default_location not in location_settings:
|
|
||||||
print(f"✓ Adding default location '{default_location}' for user '{username}'")
|
|
||||||
location_settings[default_location] = {'accessible': True}
|
|
||||||
user['locationSettings'] = location_settings
|
|
||||||
changes_made += 1
|
|
||||||
elif not location_settings[default_location].get('accessible', False):
|
|
||||||
print(f"✓ Marking default location '{default_location}' as accessible for user '{username}'")
|
|
||||||
location_settings[default_location]['accessible'] = True
|
|
||||||
changes_made += 1
|
|
||||||
else:
|
|
||||||
print(f" User '{username}' - default location already accessible")
|
|
||||||
|
|
||||||
if changes_made > 0:
|
|
||||||
# Save updated users
|
|
||||||
with open(USERS_FILE, 'w') as f:
|
|
||||||
json.dump(users, f, indent=2)
|
|
||||||
print(f"\n✓ Fixed {changes_made} user(s) and saved to users.json")
|
|
||||||
else:
|
|
||||||
print("\n✓ All users already have correct default location settings")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print("=" * 60)
|
|
||||||
print("DEFAULT LOCATION FIX UTILITY")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
fix_default_locations()
|
|
||||||
print()
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
"""
|
|
||||||
Test script to demonstrate password hashing and verification
|
|
||||||
"""
|
|
||||||
|
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
|
||||||
|
|
||||||
# Example: Creating a hashed password
|
|
||||||
plain_password = "mySecurePassword123"
|
|
||||||
hashed_password = generate_password_hash(plain_password, method='pbkdf2:sha256')
|
|
||||||
|
|
||||||
print("=" * 60)
|
|
||||||
print("PASSWORD HASHING DEMONSTRATION")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
print(f"Original Password: {plain_password}")
|
|
||||||
print()
|
|
||||||
print(f"Hashed Password: {hashed_password}")
|
|
||||||
print()
|
|
||||||
print(f"Hash Length: {len(hashed_password)} characters")
|
|
||||||
print()
|
|
||||||
print("=" * 60)
|
|
||||||
print("SECURITY FEATURES")
|
|
||||||
print("=" * 60)
|
|
||||||
print("✓ Algorithm: PBKDF2-SHA256")
|
|
||||||
print("✓ Iterations: 600,000+")
|
|
||||||
print("✓ Unique salt per password")
|
|
||||||
print("✓ Cannot be reversed to plain text")
|
|
||||||
print("✓ Industry-standard security")
|
|
||||||
print()
|
|
||||||
print("=" * 60)
|
|
||||||
print("PASSWORD VERIFICATION TEST")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Test correct password
|
|
||||||
correct = check_password_hash(hashed_password, "mySecurePassword123")
|
|
||||||
print(f"Testing correct password: {correct} ✓")
|
|
||||||
|
|
||||||
# Test incorrect password
|
|
||||||
incorrect = check_password_hash(hashed_password, "wrongPassword")
|
|
||||||
print(f"Testing incorrect password: {incorrect} ✗")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Show that the same password produces different hashes (due to unique salts)
|
|
||||||
print("=" * 60)
|
|
||||||
print("UNIQUE SALT DEMONSTRATION")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
print("Hashing the same password twice produces different hashes:")
|
|
||||||
print()
|
|
||||||
hash1 = generate_password_hash("password123", method='pbkdf2:sha256')
|
|
||||||
hash2 = generate_password_hash("password123", method='pbkdf2:sha256')
|
|
||||||
print(f"Hash 1: {hash1}")
|
|
||||||
print(f"Hash 2: {hash2}")
|
|
||||||
print()
|
|
||||||
print(f"Are they different? {hash1 != hash2}")
|
|
||||||
print("Both hashes are valid and will verify correctly!")
|
|
||||||
print(f"Hash 1 verifies: {check_password_hash(hash1, 'password123')}")
|
|
||||||
print(f"Hash 2 verifies: {check_password_hash(hash2, 'password123')}")
|
|
||||||
print()
|
|
||||||
+44
-949
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
|||||||
|
"""
|
||||||
|
Product Finder Blueprint
|
||||||
|
Handles the product quiz and recommendations
|
||||||
|
"""
|
||||||
|
from flask import Blueprint, render_template, send_from_directory
|
||||||
|
from blueprints.auth import login_required
|
||||||
|
import os
|
||||||
|
|
||||||
|
products_bp = Blueprint('products', __name__, url_prefix='/quiz')
|
||||||
|
|
||||||
|
# Get base directory
|
||||||
|
basedir = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
@products_bp.route('/')
|
||||||
|
@login_required
|
||||||
|
def product_finder():
|
||||||
|
"""Serve the product finder quiz page"""
|
||||||
|
return render_template('product_finder.html')
|
||||||
|
|
||||||
|
@products_bp.route('/css/<path:filename>')
|
||||||
|
def serve_css(filename):
|
||||||
|
"""Serve CSS files"""
|
||||||
|
return send_from_directory(os.path.join(basedir, 'static', 'css'), filename)
|
||||||
|
|
||||||
|
@products_bp.route('/js/<path:filename>')
|
||||||
|
def serve_js(filename):
|
||||||
|
"""Serve JavaScript files"""
|
||||||
|
return send_from_directory(os.path.join(basedir, 'static', 'js'), filename)
|
||||||
|
|
||||||
|
@products_bp.route('/images/<path:filename>')
|
||||||
|
def serve_images(filename):
|
||||||
|
"""Serve image files"""
|
||||||
|
return send_from_directory(os.path.join(basedir, 'static', 'images'), filename)
|
||||||
|
|
||||||
|
@products_bp.route('/data/<path:filename>')
|
||||||
|
def serve_data(filename):
|
||||||
|
"""Serve data files (JSON)"""
|
||||||
|
return send_from_directory(os.path.join(basedir, 'data'), filename)
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
User Management Blueprint
|
User Management Blueprint
|
||||||
Handles user CRUD operations
|
Handles user CRUD operations
|
||||||
"""
|
"""
|
||||||
from flask import Blueprint, render_template, request, jsonify, send_file
|
from flask import Blueprint, render_template, request, jsonify, send_file, session
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from blueprints.auth import login_required, can_user, get_current_user
|
from blueprints.auth import login_required, can_user, get_current_user
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@@ -52,7 +52,8 @@ def user_manager():
|
|||||||
"""Serve the user management page"""
|
"""Serve the user management page"""
|
||||||
if not can_user('manage_users'):
|
if not can_user('manage_users'):
|
||||||
return "<h1>403 - Permission Denied</h1><p>You don't have permission to manage users.</p>", 403
|
return "<h1>403 - Permission Denied</h1><p>You don't have permission to manage users.</p>", 403
|
||||||
return render_template('user_manager.html')
|
current_user_id = session.get('user_id', -1)
|
||||||
|
return render_template('user_manager.html', current_user_id=current_user_id)
|
||||||
|
|
||||||
@users_bp.route('/api', methods=['GET'])
|
@users_bp.route('/api', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 147 KiB |
@@ -1,44 +0,0 @@
|
|||||||
"""
|
|
||||||
Configuration file for Flask application
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
"""Base configuration"""
|
|
||||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production'
|
|
||||||
DEBUG = False
|
|
||||||
TESTING = False
|
|
||||||
|
|
||||||
# Application root for subdirectory deployments
|
|
||||||
# Defaults to '/' (root) for local development
|
|
||||||
# Set to '/product-finder' for production deployment
|
|
||||||
APPLICATION_ROOT = os.environ.get('APPLICATION_ROOT', '/')
|
|
||||||
|
|
||||||
class DevelopmentConfig(Config):
|
|
||||||
"""Development configuration"""
|
|
||||||
DEBUG = True
|
|
||||||
ENV = 'development'
|
|
||||||
# Development runs at root
|
|
||||||
APPLICATION_ROOT = os.environ.get('APPLICATION_ROOT', '/')
|
|
||||||
|
|
||||||
class ProductionConfig(Config):
|
|
||||||
"""Production configuration"""
|
|
||||||
DEBUG = False
|
|
||||||
ENV = 'production'
|
|
||||||
# Production deployed to /product-finder subdirectory
|
|
||||||
APPLICATION_ROOT = os.environ.get('APPLICATION_ROOT', '/product-finder')
|
|
||||||
# Add production-specific settings here
|
|
||||||
# DATABASE_URI = os.environ.get('DATABASE_URL')
|
|
||||||
|
|
||||||
class TestingConfig(Config):
|
|
||||||
"""Testing configuration"""
|
|
||||||
TESTING = True
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
# Configuration dictionary
|
|
||||||
config = {
|
|
||||||
'development': DevelopmentConfig,
|
|
||||||
'production': ProductionConfig,
|
|
||||||
'testing': TestingConfig,
|
|
||||||
'default': DevelopmentConfig
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"description": "Bitwise flag system for product classification",
|
|
||||||
"bit_definitions": {
|
|
||||||
"base_attributes": {
|
|
||||||
"bit_0 (1)": "Base Type = Door",
|
|
||||||
"bit_1 (2)": "Base Type = Window",
|
|
||||||
"bit_2 (4)": "Is Accessory",
|
|
||||||
"bit_3 (8)": "Specific Item Link"
|
|
||||||
},
|
|
||||||
"materials": {
|
|
||||||
"bit_4 (16)": "Material = Aluminum",
|
|
||||||
"bit_5 (32)": "Material = Vinyl"
|
|
||||||
},
|
|
||||||
"colors": {
|
|
||||||
"bit_6 (64)": "Color = Black",
|
|
||||||
"bit_7 (128)": "Color = White",
|
|
||||||
"bit_8 (256)": "Color = Bronze",
|
|
||||||
"bit_9 (512)": "Color = Tan",
|
|
||||||
"bit_10 (1024)": "Color = Mill",
|
|
||||||
"bit_11 (2048)": "Color = Sandstone"
|
|
||||||
},
|
|
||||||
"subtypes": {
|
|
||||||
"bit_12 (4096)": "Subtype = Patio Door",
|
|
||||||
"bit_13 (8192)": "Subtype = Primary Window",
|
|
||||||
"bit_14 (16384)": "Subtype = Storm Door",
|
|
||||||
"bit_15 (32768)": "Subtype = Storm Window"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"usage_examples": {
|
|
||||||
"check_if_door": "bit_value & 1",
|
|
||||||
"check_if_window": "bit_value & 2",
|
|
||||||
"check_if_aluminum": "bit_value & 16",
|
|
||||||
"check_if_white": "bit_value & 128",
|
|
||||||
"check_multiple": "(bit_value & 1) and (bit_value & 16) # Door AND Aluminum",
|
|
||||||
"check_subtype_patio_door": "bit_value & 4096",
|
|
||||||
"check_subtype_primary_window": "bit_value & 8192",
|
|
||||||
"check_subtype_storm_door": "bit_value & 16384",
|
|
||||||
"check_subtype_storm_window": "bit_value & 32768"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
Product_Name,Image_URL,Product_Type,Notes
|
|
||||||
Brass Lever,http://columbiawindows.com/wp-content/uploads/2014/12/CR2DLEC.jpg,Hardware,
|
|
||||||
Brass Lever (White),http://columbiawindows.com/wp-content/uploads/2014/12/CR2DLEC.jpg,Hardware,Also comes in white
|
|
||||||
Brass Lever (MT Gold),http://columbiawindows.com/wp-content/uploads/2014/12/MT-MET-GOLD.jpg,Hardware,
|
|
||||||
Brass Lever (550 Gold),http://columbiawindows.com/wp-content/uploads/2014/12/550-met-gold.jpg,Hardware,
|
|
||||||
Brass Pull,http://columbiawindows.com/wp-content/uploads/2014/12/dx-ecoat.jpg,Hardware,
|
|
||||||
Brass Pull (White),http://columbiawindows.com/wp-content/uploads/2014/12/CR3SEC.jpg,Hardware,Also comes in white
|
|
||||||
Brass Pull (MT Gold),http://columbiawindows.com/wp-content/uploads/2014/12/MT-MET-GOLD.jpg,Hardware,
|
|
||||||
Brass Pull (550 Gold),http://columbiawindows.com/wp-content/uploads/2014/12/550-met-gold.jpg,Hardware,
|
|
||||||
Satin Pull,http://columbiawindows.com/wp-content/uploads/2014/12/dx-satin-finish.jpg,Hardware,
|
|
||||||
Satin Pull (Silver),http://columbiawindows.com/wp-content/uploads/2014/12/MT-satin-silver.jpg,Hardware,
|
|
||||||
Black Pull Handle,http://columbiawindows.com/wp-content/uploads/2014/12/VPBLACK.jpg,Hardware,
|
|
||||||
White Pull Handle,http://columbiawindows.com/wp-content/uploads/2014/12/VPWHITE.jpg,Hardware,
|
|
||||||
Bevel Cut Insert,http://columbiawindows.com/wp-content/uploads/2014/12/bevel.jpg,Insert,
|
|
||||||
Brass Kick Panel,http://columbiawindows.com/wp-content/uploads/2014/12/brasskp.jpg,Panel,
|
|
||||||
|
@@ -1,68 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"username": "admin_user",
|
|
||||||
"password": "pbkdf2:sha256:1000000$examplehash$...",
|
|
||||||
"defaultLocation": "LINDS",
|
|
||||||
"active": true,
|
|
||||||
"permissions": {
|
|
||||||
"manage_users": true,
|
|
||||||
"view_reports": true,
|
|
||||||
"create_quotes": true
|
|
||||||
},
|
|
||||||
"locationSettings": {
|
|
||||||
"LINDS": {
|
|
||||||
"accessible": true,
|
|
||||||
"permissions": {
|
|
||||||
"manage_inventory": true,
|
|
||||||
"approve_quotes": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"IOLA": {
|
|
||||||
"accessible": true,
|
|
||||||
"permissions": {
|
|
||||||
"manage_inventory": false,
|
|
||||||
"approve_quotes": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"KC": {
|
|
||||||
"accessible": false
|
|
||||||
},
|
|
||||||
"BMD": {
|
|
||||||
"accessible": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "standard_user",
|
|
||||||
"password": "pbkdf2:sha256:1000000$examplehash2$...",
|
|
||||||
"defaultLocation": "KC",
|
|
||||||
"active": true,
|
|
||||||
"permissions": {
|
|
||||||
"manage_users": false,
|
|
||||||
"view_reports": true,
|
|
||||||
"create_quotes": true
|
|
||||||
},
|
|
||||||
"locationSettings": {
|
|
||||||
"LINDS": {
|
|
||||||
"accessible": true,
|
|
||||||
"permissions": {
|
|
||||||
"manage_inventory": false,
|
|
||||||
"approve_quotes": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"IOLA": {
|
|
||||||
"accessible": true
|
|
||||||
},
|
|
||||||
"KC": {
|
|
||||||
"accessible": true,
|
|
||||||
"permissions": {
|
|
||||||
"manage_inventory": false,
|
|
||||||
"approve_quotes": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"BMD": {
|
|
||||||
"accessible": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
{
|
|
||||||
"cobrai": {
|
|
||||||
"sourceImage": "images/cobrai",
|
|
||||||
"productCode": "COBRAI",
|
|
||||||
"description": "Storm Door - Full View with Screen",
|
|
||||||
"availableColors": {
|
|
||||||
"white": {"rgb": [255, 255, 255], "name": "White"},
|
|
||||||
"black": {"rgb": [30, 30, 30], "name": "Black"},
|
|
||||||
"bronze": {"rgb": [110, 80, 50], "name": "Bronze"},
|
|
||||||
"sandstone": {"rgb": [210, 190, 165], "name": "Sandstone"}
|
|
||||||
},
|
|
||||||
"colorableRegions": [
|
|
||||||
{
|
|
||||||
"name": "region_name",
|
|
||||||
"topLeft": [24, 23],
|
|
||||||
"bottomRight": [505, 1022],
|
|
||||||
"description": "Width: 481px, Height: 999px"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "frame",
|
|
||||||
"topLeft": [0, 0],
|
|
||||||
"bottomRight": [100, 1450],
|
|
||||||
"description": "Vertical frame left side"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "frame_right",
|
|
||||||
"topLeft": [620, 0],
|
|
||||||
"bottomRight": [720, 1450],
|
|
||||||
"description": "Vertical frame right side"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "top_rail",
|
|
||||||
"topLeft": [0, 0],
|
|
||||||
"bottomRight": [720, 80],
|
|
||||||
"description": "Top horizontal rail"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mid_rail",
|
|
||||||
"topLeft": [0, 590],
|
|
||||||
"bottomRight": [720, 680],
|
|
||||||
"description": "Middle horizontal rail"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bottom_panel",
|
|
||||||
"topLeft": [100, 1090],
|
|
||||||
"bottomRight": [620, 1450],
|
|
||||||
"description": "Bottom kickplate panel"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"hardwarePositions": {
|
|
||||||
"handle_right": {
|
|
||||||
"x": 580,
|
|
||||||
"y": 730,
|
|
||||||
"image": "images/hardware/handle-lever.png"
|
|
||||||
},
|
|
||||||
"handle_left": {
|
|
||||||
"x": 140,
|
|
||||||
"y": 730,
|
|
||||||
"image": "images/hardware/handle-lever.png",
|
|
||||||
"flip": true
|
|
||||||
},
|
|
||||||
"lock": {
|
|
||||||
"x": 360,
|
|
||||||
"y": 800,
|
|
||||||
"image": "images/hardware/lock.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"glassRegions": [
|
|
||||||
{
|
|
||||||
"name": "top_glass",
|
|
||||||
"topLeft": [100, 80],
|
|
||||||
"bottomRight": [620, 590],
|
|
||||||
"description": "Top screen/glass area"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bottom_glass",
|
|
||||||
"topLeft": [100, 680],
|
|
||||||
"bottomRight": [620, 1090],
|
|
||||||
"description": "Bottom screen/glass area"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"imageWidth": 720,
|
|
||||||
"imageHeight": 1450,
|
|
||||||
"defaultColor": "white",
|
|
||||||
"defaultHinge": "right"
|
|
||||||
},
|
|
||||||
"cache": {
|
|
||||||
"enabled": true,
|
|
||||||
"directory": "cache/product_images",
|
|
||||||
"maxAge": 86400
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,922 +0,0 @@
|
|||||||
PROD_CODE,BIT_VALUE,BIT_HEX,BIT_BINARY,DISCONTINUED,FLAGS
|
|
||||||
1650-10,16,0x10,10000,False,Aluminum
|
|
||||||
404,35282,0x89D2,1000100111010010,False,Window | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Window
|
|
||||||
450,35282,0x89D2,1000100111010010,False,Window | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Window
|
|
||||||
606,35282,0x89D2,1000100111010010,False,Window | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Window
|
|
||||||
650,33234,0x81D2,1000000111010010,False,Window | Aluminum | Black | White | Bronze | Subtype:Storm Window
|
|
||||||
1400,160,0xA0,10100000,False,Vinyl | White
|
|
||||||
1500,8354,0x20A2,10000010100010,False,Window | Vinyl | White | Subtype:Primary Window
|
|
||||||
1510,160,0xA0,10100000,False,Vinyl | White
|
|
||||||
1650,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
1700,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
1710,16,0x10,10000,False,Aluminum
|
|
||||||
2000,8594,0x2192,10000110010010,False,Window | Aluminum | White | Bronze | Subtype:Primary Window
|
|
||||||
2100,16,0x10,10000,False,Aluminum
|
|
||||||
2200,16,0x10,10000,False,Aluminum
|
|
||||||
2650,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
2700,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
2710,16,0x10,10000,False,Aluminum
|
|
||||||
3000,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
3100,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
3200,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
3300,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
3302,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
3303,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
3310,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
3700,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
3710,16,0x10,10000,False,Aluminum
|
|
||||||
4700,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
4710,16,0x10,10000,False,Aluminum
|
|
||||||
5200,4769,0x12A1,1001010100001,False,Door | Vinyl | White | Tan | Subtype:Patio Door
|
|
||||||
5700,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
6700,16,0x10,10000,False,Aluminum
|
|
||||||
265010,16,0x10,10000,False,Aluminum
|
|
||||||
1400SCR,144,0x90,10010000,False,Aluminum | White
|
|
||||||
1650SCR,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
1650VS,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
1700CSC,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
1700CV,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
1700ESC,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
1700EV,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
1700SCR,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
1700VS,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
2000SCR,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
2100EV,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
2200PDG,16,0x10,10000,False,Aluminum
|
|
||||||
2650SCR,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
2650VP,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
2700CV,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
2700EV,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
2700SCR,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
2700VS,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
3000SCR,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
305INS,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
306INS,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
3100EV,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
3100SCR,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
310PSCR,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
3700SCR,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
3700VP,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
404ONE,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
450ONE,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
4700SCR,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
606ONE,1488,0x5D0,10111010000,False,Aluminum | Black | White | Bronze | Mill
|
|
||||||
6100I,16785,0x4191,100000110010001,False,Door | Aluminum | White | Bronze | Subtype:Storm Door
|
|
||||||
650ONE,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
8100I,16785,0x4191,100000110010001,False,Door | Aluminum | White | Bronze | Subtype:Storm Door
|
|
||||||
BELMONT,672,0x2A0,1010100000,False,Vinyl | White | Tan
|
|
||||||
BGI,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
BGI404,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
BGI606,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
BGIST,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
C-500,2448,0x990,100110010000,False,Aluminum | White | Bronze | Sandstone
|
|
||||||
C1150,160,0xA0,10100000,False,Vinyl | White
|
|
||||||
C1621,16,0x10,10000,False,Aluminum
|
|
||||||
C1622,16,0x10,10000,False,Aluminum
|
|
||||||
C1626VA,16,0x10,10000,False,Aluminum
|
|
||||||
C1710,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
C1721,16,0x10,10000,False,Aluminum
|
|
||||||
C1722,16,0x10,10000,False,Aluminum
|
|
||||||
C1724,16,0x10,10000,False,Aluminum
|
|
||||||
C1800,33170,0x8192,1000000110010010,False,Window | Aluminum | White | Bronze | Subtype:Storm Window
|
|
||||||
C2021,16,0x10,10000,False,Aluminum
|
|
||||||
C2022,16,0x10,10000,False,Aluminum
|
|
||||||
C2026,16,0x10,10000,False,Aluminum
|
|
||||||
C2710,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
C300,16,0x10,10000,False,Aluminum
|
|
||||||
C3221,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
C3222,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
C3223,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
C3224,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
C3300,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C3700,16,0x10,10000,False,Aluminum
|
|
||||||
C400,160,0xA0,10100000,False,Vinyl | White
|
|
||||||
C500,2448,0x990,100110010000,False,Aluminum | White | Bronze | Sandstone
|
|
||||||
C500GL,0,0x0,0,False,None
|
|
||||||
C500GLP,0,0x0,0,False,None
|
|
||||||
C500PP,2448,0x990,100110010000,False,Aluminum | White | Bronze | Sandstone
|
|
||||||
C500SCR,2448,0x990,100110010000,False,Aluminum | White | Bronze | Sandstone
|
|
||||||
C500VP,2448,0x990,100110010000,False,Aluminum | White | Bronze | Sandstone
|
|
||||||
C521,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C522,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C526,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C900,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C900EV,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C900SCR,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C910,16,0x10,10000,False,Aluminum
|
|
||||||
C910PP,16,0x10,10000,False,Aluminum
|
|
||||||
C921,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C922,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C924,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C931,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C939,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C940,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C949,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
C960,16,0x10,10000,False,Aluminum
|
|
||||||
COBRAI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door
|
|
||||||
COBRATI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door
|
|
||||||
CRWNFVI,16401,0x4011,100000000010001,False,Door | Aluminum | Subtype:Storm Door
|
|
||||||
CRWNSDI,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone
|
|
||||||
D770,8354,0x20A2,10000010100010,False,Window | Vinyl | White | Subtype:Primary Window
|
|
||||||
DSGLASS,0,0x0,0,False,None
|
|
||||||
DURASEA,0,0x0,0,False,None
|
|
||||||
EXPAND,16,0x10,10000,False,Aluminum
|
|
||||||
FULLSCR,16,0x10,10000,False,Aluminum
|
|
||||||
FVGI,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone
|
|
||||||
FVSI,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone
|
|
||||||
GOLIATH,16401,0x4011,100000000010001,False,Door | Aluminum | Subtype:Storm Door
|
|
||||||
HERCULE,16401,0x4011,100000000010001,False,Door | Aluminum | Subtype:Storm Door
|
|
||||||
IMPERIL,4561,0x11D1,1000111010001,False,Door | Aluminum | Black | White | Bronze | Subtype:Patio Door
|
|
||||||
INSGLAS,0,0x0,0,False,None
|
|
||||||
ISP,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze
|
|
||||||
IVP,4561,0x11D1,1000111010001,False,Door | Aluminum | Black | White | Bronze | Subtype:Patio Door
|
|
||||||
JET,4497,0x1191,1000110010001,False,Door | Aluminum | White | Bronze | Subtype:Patio Door
|
|
||||||
JSP,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
JVP,4497,0x1191,1000110010001,False,Door | Aluminum | White | Bronze | Subtype:Patio Door
|
|
||||||
KINGDVI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door
|
|
||||||
KINGFSC,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone
|
|
||||||
KINGI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door
|
|
||||||
KINGSDI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door
|
|
||||||
M1200,4497,0x1191,1000110010001,False,Door | Aluminum | White | Bronze | Subtype:Patio Door
|
|
||||||
M306,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
OUTSIDE,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone
|
|
||||||
PATIOSC,6609,0x19D1,1100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Patio Door
|
|
||||||
PDSCRTT,6609,0x19D1,1100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Patio Door
|
|
||||||
PRPDS,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone
|
|
||||||
PRSCR,3984,0xF90,111110010000,False,Aluminum | White | Bronze | Tan | Mill | Sandstone
|
|
||||||
PRSCR11,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
PSINS,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
PWS,4048,0xFD0,111111010000,False,Aluminum | Black | White | Bronze | Tan | Mill | Sandstone
|
|
||||||
PWSINS,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
R1150,160,0xA0,10100000,False,Vinyl | White
|
|
||||||
R1400,8354,0x20A2,10000010100010,False,Window | Vinyl | White | Subtype:Primary Window
|
|
||||||
R1500,8354,0x20A2,10000010100010,False,Window | Vinyl | White | Subtype:Primary Window
|
|
||||||
R1510,32,0x20,100000,False,Vinyl
|
|
||||||
R2000,8594,0x2192,10000110010010,False,Window | Aluminum | White | Bronze | Subtype:Primary Window
|
|
||||||
R2100,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
R2100EV,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
R2200,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
R300,16,0x10,10000,False,Aluminum
|
|
||||||
R3302,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
R400,160,0xA0,10100000,False,Vinyl | White
|
|
||||||
R770,8354,0x20A2,10000010100010,False,Window | Vinyl | White | Subtype:Primary Window
|
|
||||||
R770SCR,16,0x10,10000,False,Aluminum
|
|
||||||
RCKTRAP,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
REWIRE,16,0x10,10000,False,Aluminum
|
|
||||||
RNDROCK,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
ROCKET,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
RROCKET,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
SCRI,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
SCRI404,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
SCRI606,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
SCRI808,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
SSGLASS,0,0x0,0,False,None
|
|
||||||
SSSCR,16,0x10,10000,False,Aluminum
|
|
||||||
TBGI,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone
|
|
||||||
TBR,4561,0x11D1,1000111010001,False,Door | Aluminum | Black | White | Bronze | Subtype:Patio Door
|
|
||||||
TGI,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill
|
|
||||||
TGI404,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
TGI606,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone
|
|
||||||
TGIODD,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone
|
|
||||||
THOR,16401,0x4011,100000000010001,False,Door | Aluminum | Subtype:Storm Door
|
|
||||||
TVI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door
|
|
||||||
VKI,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone
|
|
||||||
VP3700,400,0x190,110010000,False,Aluminum | White | Bronze
|
|
||||||
WINDGAT,672,0x2A0,1010100000,False,Vinyl | White | Tan
|
|
||||||
ZBARS,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone
|
|
||||||
COBRATK,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
,0,0x0,0,False,None
|
|
||||||
|
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"COBRAI": "1100",
|
|
||||||
"COBRATI": "1100",
|
|
||||||
"TVI": "1100",
|
|
||||||
"TRTI": "1100",
|
|
||||||
"1400": "1400",
|
|
||||||
"1500": "1500",
|
|
||||||
"1650": "1650",
|
|
||||||
"2000": "2000",
|
|
||||||
"1700": "1700",
|
|
||||||
"2100": "2100",
|
|
||||||
"2700": "2700",
|
|
||||||
"3100": "3100",
|
|
||||||
"1510": "1510",
|
|
||||||
"2200": "2200",
|
|
||||||
"3200": "3200",
|
|
||||||
"3300": "3300",
|
|
||||||
"3302": "3302",
|
|
||||||
"3303": "3303",
|
|
||||||
"3310": "3310",
|
|
||||||
"2400": "2400"
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,998 +0,0 @@
|
|||||||
,,,,,,Base,Sub-type,,Accessory,,Materials,,Colors,,,,,,
|
|
||||||
DISCONT,LOC_CODE,PROD_CODE,CATEGORY,CATEGORY_NEW,DESCRIPTION,Type,Door,Window,Yes,This Item,Aluminum,Vinyl,Black,White,Bronze,Tan,Mill,Sandstone,~
|
|
||||||
FALSE,Iola,1650-10,FPPW,,#1650-10 INSULATED FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,303,STORMS,,#303 DART STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,350,STORMS,,#350 ARROW STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,404,STORMS,,#404 FALCON STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,450,STORMS,,#450 RAVEN STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,606,STORMS,,#606 LION STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,650,STORMS,,#650 LYNX STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,808,STORMS,,808 HAWK STORM WINDOW,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,850,STORMS,,#850 KENT STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,1400,SHPW,,SERIES 1400 VINYL SLIDING PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,1500,SHPT,,SERIES 1500 S.H. TILT VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,1510,FPPW,,1510 VINYL INSULATED FIXED LITE,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,1650,SHPW,,#1650 INSULATED SINGLE HUNG,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,1700,SHPW,,#1700 INSULATED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,1710,FPPW,,C-1710 FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,2000,SHPT,,SERIES 2000 S.H. T.B. TILT PRIMARY,Window,,Primary Window,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,2100,SHPT,,SERIES 2100 THERMAL BREAK SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,2200,FPPW,,SERIES 2200 T.B. FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,2400,PD,,2400 ROYAL CROWN PATIO DOOR,Door,Patio Door,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,2650,SHPW,,#2650 SINGLE HUNG SINGLE GLAZED PRIME,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,2700,SHPW,,#2700 SINGLE GLAZED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,2710,FPPW,,C-2710 FIXED LITE - SINGLE GLAZED,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3000,3000DHP,#N/A,SERIES 3000 D.H. T.B REPLACEMENT WD,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3100,3000DHP,#N/A,SERIES 3100 T.B. REPLACEMENT SLIDER,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3200,3000FPP,#N/A,SERIES 3200 T.B. PICTURE WINDOW,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3300,CASEMNT,,3300 1-PANEL T.B. CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3302,CASEMNT,,3300 2-PANEL T.B. CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3303,CASEMNT,,3300 3-PANEL T.B. CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3310,CASEMNT,,3310 FIXED CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,3400,CASEMNT,,3400 1 PANEL VINYL CLAD CASEMENT,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,3402,CASEMNT,,3400 2 PANEL VINYL CLAD CASEMENT,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,3403,CASEMNT,,3400 3 PANEL VINYL CLAD CASEMENT,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,3500,CASEMNT,,3500 1-PANEL WOOD INTERIOR CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,3502,CASEMNT,,3500 2-PANEL WOOD INTERIOR CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,3503,CASEMNT,,3503 3-PANEL WOOD INTERIOR CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3700,SHPW,,#3700 INSULATED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3710,FPPW,,C-3710 INSULATED FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,4100,REPLACE,,SERIES 4100 VINYL SLIDER,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,4700,SHPW,,#4700 SINGLE GLAZED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,4710,FPPW,,#4710 SINGLE GLAZED FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,5200,PD,,5200 VINYL PATIO DOORS,Door,Patio Door,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,5700,SHPW,,#5700 INSULATED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,6300,SHPT,,SERIES 6300 S.H. TILT VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,6301,SHPW,,SERIES 6301 VINYL SLIDING PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,6700,SHPW,,#6700 SINGLE GLAZED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,265010,FPPW,,#2650-10 SINGLE GLAZED FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,1400SCR,SHPW,,1400 SCREEN,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,1650SCR,SCREENS,,SCREENS FOR #1650,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,1650VS,SHPW,,1650 BOTTOM SASH,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,1700CSC,SCREENS,,SCREEN FOR #1700 CENTER VENT SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,1700CV,SHPW,,1700 CENTER VENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,1700ESC,SCREENS,,SCREEN FOR #1700 ENDS VENT SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,1700EV,SHPW,,1700 END VENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,1700SCR,SCREENS,,SCREEN FOR #1700 SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,1700VS,SHPW,,#1700 VENT SASH,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,2000SCR,SHPT,,C-2000 SCREEN,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,2100EV,2000SLI,#N/A,SERIES 2100 3-PANEL SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,2200PDG,FPPW,,SERIES 2200 TB FIXED LITE W/TEMP GLASS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,2650SCR,SCREENS,,SCREENS FOR 2650,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,2650VP,SHPW,,#2650 VENT PANEL - SINGLE HUNGE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,2700CV,SHPW,,C-2700 - CENTER VENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,2700EV,SHPW,,C-2700 - END VENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,2700SCR,SCREENS,,SCREENS FOR #2700,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,2700VS,SHPW,,#2700 VENT SASH,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,3000SCR,SCREENS,,SCREENS FOR #3000,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,305INS,INSERTS,,#305 SASH WINDCHECK INSERTS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,306INS,INSERTS,,#306 SCHLEGEL GLASS INSERTS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,3100EV,3000DHP,#N/A,SERIES 3100 3-PANEL SLIDER,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3100SCR,SCREENS,,SCREENS FOR #3100,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,310PSCR,SCRINS,,#310 PLAIN SCREENS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,3700SCR,SCREENS,,SCREEN FOR #3700 POLE BARN WD,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,3700VP,SHPW,,#3700 VENT PANEL,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,404ONE,STPW,,#404 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,450ONE,STPW,,#450 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,4700SCR,SCREENS,,SCREENS FOR #4700,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,5000SCR,SCREENS,,SCREEN FOR R5000 SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,606ONE,STPW,,#606 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,6100I,STD,,STAR 6100 FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,6100L,STD,,STAR 6100 FULL VIEW STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,650ONE,STPW,,#650 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
TRUE,Iola,7100I,STD,,STAR 7100 FULL VIEW SELF STORING DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,7100L,STD,,STAR 7100 FULL VIEW SELF STORING DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,808ONE,STPW,,#808 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,8100I,STD,,STAR 8100 FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,8100L,STD,,STAR 8100 FULL VIEW STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,850ONE,STPW,,#850 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,BELMONT,3000FPP,#N/A,BELMONT ALLIANCE DOUBLE HUNG WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,BGI,INSERTS,,BOTTOM GL INSERTS FOR ECONOMY ST WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,BGI404,INSERTS,,BOTTOM GL INSERTS FOR #404/450 ST WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,BGI606,INSERTS,,BOTTOM GL INSERTS FOR #606/650 ST WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,BGIST,INSERTS,,INSERTS FOR STORM DOORS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,C-500,SHPW,,C-500 SH THERMAL BREAK INS.,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,C1150,CASEMNT,,C1150-VINYL AWNING WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C1500AR,CIR TOP,,C-1510 VINYL ARCH TOP-OPERATING WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C1510AR,CIR TOP,,C-1510 VINYL ARCH TOP WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C1521,CIR TOP,,C-1521 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C1526,CIR TOP,,C-1526 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C1621,CIR TOP,,C-1621 INSULATED CIRCLE TOP,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C1622,CIR TOP,,C-1622 INSULATED CIRCLE TOP,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C1626VA,CIR TOP,,C-1626VA INSULATED CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C1710,SHPW,,#1710 PICTURE OVER SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,C1721,CIR TOP,,C-1721 INSULATED CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C1722,CIR TOP,,C-1722 INSULATED CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C1724,CIR TOP,,C-1724 INSULATED CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C1800,C1800,#N/A,C-1800 INSIDE SLIDING STORM WINDOW,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C2021,CIR TOP,,C-2021 T.B CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C2022,CIR TOP,,C-2022 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C2026,CIR TOP,,C-2026 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C2710,SHPW,,#2710 PICTURE OVER SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,C300,BSMT,#N/A,C-300 ALUM INSERTS FOR BASEMENT BUCKS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C3221,CIR TOP,,C-3221 T.B CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C3222,CIR TOP,,C-3222 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C3223,CIR TOP,,C-3223 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C3224,CIR TOP,,C-3224 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C3300,CASEMNT,,3300 1-PANEL T.B. CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C3700,SHPW,,C-3700 INSULATED SLIDING POLE BARN WND,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C400,BSMT,#N/A,C-400 VINYL INSERT FOR BASEMENT BUCKS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C4000,REPLACE,,C-4000 SINGLE HUNGE PRIMARY WINDOW,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C4260,PD,,C-4260 STEEL MIRROR DOOR,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C500,SHPW,,C-500 INS THERMAL BREAK SINGLE HUNG,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,C500GL,VENTS,,LITES OF INSULATED GLASS FOR C-500'S,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C500GLP,VENTS,,LITES OF INSULATED GLASS FOR C-500'S,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C500PP,SHPW,,C-500 INS THERMAL BREAK SINGLE HUNG,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,C500SCR,SCREENS,,C-500 SCREEN,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,C500VP,VENTS,,VENT PANELS FOR C-500,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,C521,CIR TOP,,C-521 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C522,CIR TOP,,C-522 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C526,CIR TOP,,C-526 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C610,CASEMNT,,C610 VINYL CASEMENT WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C620,FPPW,,C620 VINYL AWNING WINDOWS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C621,CIR TOP,,C-621 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C626,CIR TOP,,C-626 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C640,FPPW,,C-640 VINYL FIXED CASEMENT WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C826,CIR TOP,,C-826 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C828,CIR TOP,,C-828 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C8321,CIR TOP,,C-8321 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,C8326,CIR TOP,,C-8326 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C900,SHPW,,C-900 INS THERMAL BREAK SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C900EV,SHPW,,C-900 ENDS VENT SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C900SCR,SCREENS,,SCREEN FOR C-900,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C910,FPPW,,C-910 INSULATED T.B. FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C910PP,FPPW,,C-910 ARCH TOP,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C921,CIR TOP,,C-921 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C922,CIR TOP,,C-922 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C924,CIR TOP,,C-924 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C931,CIR TOP,,C-931 T.B. ROUND PRIMARY WINDOW,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C939,CIR TOP,,C-939 T.B. ROUND PRIMARY WINDOW,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C940,CIR TOP,,C-940 T.B. OCTAGON PRIMARY,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C949,CIR TOP,,C949 OCTAGON THERMAL BREAK WINDOW,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,C960,FPPW,,C-960 INSULATED T.B. CIRCLE TOP,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,COBRAI,STD,,COLUMBIA COBRA STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
TRUE,Iola,COBRAL,STD,,COLUMBIA COBRA STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,COBRATI,STD,,COLUMBIA COBRA STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
TRUE,Iola,COBRATL,STD,,COLUMBIA COBRA STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,CRWNFVI,STD,,CROWN FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,CRWNFVL,STD,,CROWN FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,CRWNSDI,STD,,COLUMBIA CROWN SCREEN DOOR,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
TRUE,Iola,CRWNSDL,STD,,COLUMBIA CROWN SCREEN DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,D770,SHPT,,D770 D.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,D780,SHPW,,D780 DOUBLE SLIDE VINYL PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,D830,SHPT,,D830 D.H.TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,D830SCR,SHPT,,D830 SCREEN,Window,,Primary Window,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,D832,FPPW,,D832 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,DSGLASS,GLASS,,DOUBLE STRENGTH GLASS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,DURASEA,FPPW,,"DURASEAL 5/8"" (GRAY) PER REEL",,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,EXPAND,EXPANDR,,SILL EXPANDERS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,FULLSCR,SCREENS,,FULL SCREEN,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,FV10I,STD,,KING FV-10 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,FV10L,STD,,KING FV-10 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,FV3I,STD,,KING FV-3 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,FV3L,STD,,KING FV-3 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,FVGI,INSERTS,,GLASS INSERTS FOR KING ONE LITE,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,FVSI,INSERTS,,FULL SCREENS ONLY FOR KING ONE-LITES,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
TRUE,Iola,G3000,GARDEN,,COLUMBIA COMFORT 3000 VINYL GARDEN WD,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,GOLIATH,STD,,GOLIATH STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,HERCULE,STD,,HERCULES STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,IMPERIL,PD,,IMPERIAL PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,INSGLAS,GLASS,,INSULATED GLASS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,ISP,PD,,STATIONARY PANELS FOR IMPERIAL DOORS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,IVP,PD,,VENT PANELS FOR IMPERIAL PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,JET,PD,,COLUMBIA JET PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,JSP,PD,,STATIONARY PANELS FOR JET DOORS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,JVP,PD,,VENT PANELS FOR JET PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,KINGDVI,STD,,KING DUAL VENT STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
TRUE,Iola,KINGDVL,STD,,KING DUAL VENT STORM DOORS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,KINGFSC,INSERTS,,FULL SCREEN FOR KING ONE-LITE,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,KINGI,STD,,KING ONE-LITE STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
TRUE,Iola,KINGL,STD,,KING ONE-LITE STORM DOORS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,KINGSDI,STD,,KING ONE-LITE SCREEN DOOR ONLY,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
TRUE,Iola,KINGSDL,STD,,KING ONE-LITE SCREEN DOOR ONLY,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,LINCOLN,3000DHP,#N/A,LINCOLN PRIMARY WOOD WINDOWS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,LINCPDR,PD,,LINCOLN FRENCH PATIO DOOR,Door,Patio Door,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,M1200,PD,,M1200 PATIO STORM DOOR,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,M306,INSERTS,,M-306 SCHLEGEL GLASS INSERTS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,OUTSIDE,STDMISC,,OUTSIDE DOOR SWEEPS - ALUM + VINYL,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,PATIOSC,SCREENS,,PATIO DOOR SCREENS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,PDSCRTT,SCREENS,,SPECIAL SIZE SCREENS FOR PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,PRPDS,SCREENS,,SCREENS MADE FROM PLAIN PATIO SCR RAIL,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,PRSCR,PSCREEN,,SCREENS FOR PRIME MADE FROM #19-88,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,PRSCR11,PSCREEN,,SCREEN FOR PRIME MADE FROM #19-11,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,PSINS,INSERTS,,PLAIN SASH INSERTS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,PWS,STPW,,PIN-ON PICTURE WINDOWS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,PWSINS,INSERTS,,INSERTS ONLY FOR PIN-ON PICTURE WINDOW,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,R1150,REPLACE,,R-1150 VINYL AWNING WINDOWS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R1400,REPLACE,,SERIES 1400 VINYL SLIDING PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R1500,REPLACE,,SERIES 1500 S.H. TILT VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R1510,FPPW,,SERIES 1510 FIXED LITE VINYL PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R2000,REPLACE,,SERIES 2000 S.H. T.B. TILT PRIMARY,Window,,Primary Window,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R2100,REPLACE,,SERIES 2100 THERMAL BREAK SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R2100EV,R2000SL,,SERIES 2100 3-PANEL T.B. SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R2200,RFPPW,,SERIES 2200 T.B. FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R300,RBSMT,,C-300 ALUM INSERTS FOR BASEMENT BUCKS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R3302,CASEMNT,,3302 2-PANEL T.B. CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R400,RBSMT,,C-400 VINYL INSERT FOR BASEMENT BUCKS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,R5000,REPLACE,,R-5000 INSULATED ALUMINUM SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R770,REPLACE,,R770 D.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,R770SCR,SCREENS,,FULL SCREEN,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,R780,REPLACE,,R780 VINYL SLIDING PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,R820,REPLACE,,R820 S.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,R821,REPLACE,,S821 SINGLE SLIDE VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,R822,FPPW,,S822 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,R830,REPLACE,,R830 D.H.TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,R832,FPPW,,R832 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,RCKTRAP,INSERTS,,ROCKET TRAPEZOIDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,REWIRE,SCREENS,,REWIRED SCREENS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,RNDROCK,INSERTS,,ROUND ROCKET INSERT,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,ROCKET,INSERTS,,ROCKET INSERTS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
TRUE,Iola,ROYAL,STD,,COLUMBIA ROYAL STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,RROCKET,INSERTS,,RADIUS ROCKETS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
TRUE,Iola,S820,SHPT,,S820 S.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,S821,SHPW,,S821 SINGLE SLIDE VINYL PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,S822,FPPW,,S822 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,SCRI,INSERTS,,SCREEN INSERTS FOR ECONOMY STORM WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,SCRI404,INSERTS,,SCREEN INSERTS FOR #404/450 STORM WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,SCRI606,INSERTS,,SCREEN INSERTS FOR #606/650 STORM WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,SCRI808,INSERTS,,SCREEN INSERTS FOR #808/850 STORM WIND,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
TRUE,Iola,SS10I,STD,,COBRA SS-10 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,SS10L,STD,,COBRA SS-10 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,SS3I,STD,,COBRA SS-3 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,SS3L,STD,,COBRA SS-3 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,SSGLASS,GLASS,,SINGLE STRENGTH GLASS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,SSSCR,INSERTS,,SCREEN INSERT FOR SELF STORING DOORS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,TBGI,INSERTS,,TEMPERED GLASS INSERTS FOR SS DOORS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,TBR,PD,,IMPERIAL T.B.R. REPLACEMENT PATIO DOOR,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,TGI,INSERTS,,TOP GL INSERTS FOR ECONOMY ST WDS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,
|
|
||||||
FALSE,Iola,TGI404,INSERTS,,TOP GLASS INSERTS FOR #404/450 ST WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,TGI606,INSERTS,,TOP GLASS INSERTS FOR #606/650 ST WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,
|
|
||||||
FALSE,Iola,TGIODD,INSERTS,,TOP GLASS INSERTS FOR STORM DOORS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,THOR,STD,,THOR - STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,TIARAI,STD,,COLUMBIA TIARA SELF STORING STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,TIARAL,STD,,COLUMBIA TIARA SELF STORING STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,TVGROOV,INSERTS,,TEMPERED V-GROOVE ONE-LITE INSERTS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,TVI,STD,,COLUMBIA COBRA TWIN VENT STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
TRUE,Iola,TVL,STD,,COLUMBIA COBRA TWIN VENT STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,VKI,STOCK,,VENTILATOR KICKPANEL FOR KING ONELITE,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
TRUE,Iola,VKS,STOCK,,VENTILATOR SCREEN FOR KING ONE LITE,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,Iola,VP3700,VENTS,,VENT PANELS FOR #3700,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,WINDGAT,VACW,,ALLIANCE WINDGATE CASEMENT WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,
|
|
||||||
TRUE,Iola,XBUCKIN,INSERTS,,TEMPERED GLASS INSERTS FOR CROSSBUCKS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,Iola,ZBARS,STDMISC,,Z-BARS FOR STORM DOORS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,KC,COBRATK,STD,,Cobra aluminum storm door with Glass Kick Panel,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,
|
|
||||||
|
+18
-55
@@ -4,12 +4,7 @@
|
|||||||
"password": "pbkdf2:sha256:1000000$tPRmseRGKLveXTxS$87eda9a20db29d12838fcb607f1f3536141f743ec9879702dc2a0dd893b4078d",
|
"password": "pbkdf2:sha256:1000000$tPRmseRGKLveXTxS$87eda9a20db29d12838fcb607f1f3536141f743ec9879702dc2a0dd893b4078d",
|
||||||
"defaultLocation": "KC",
|
"defaultLocation": "KC",
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"manage_users": true,
|
"manage_users": true
|
||||||
"view_reports": true,
|
|
||||||
"create_quotes": true,
|
|
||||||
"approve_quotes": true,
|
|
||||||
"manage_products": true,
|
|
||||||
"manage_inventory": true
|
|
||||||
},
|
},
|
||||||
"locationSettings": {
|
"locationSettings": {
|
||||||
"LINDS": {
|
"LINDS": {
|
||||||
@@ -25,74 +20,42 @@
|
|||||||
"accessible": true
|
"accessible": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active": true
|
"active": true,
|
||||||
|
"superAdmin": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"username": "Darlene",
|
"username": "Darlene",
|
||||||
"password": "pbkdf2:sha256:1000000$8sblSWLWIvsjcta2$d974d13956f67cc42e1fa88191d68c896d4daa663ae2d76d7988beb569647d27",
|
"password": "pbkdf2:sha256:1000000$TSAG3KNMWuEQr3Jl$7e8eda7495bc11a43161df75465ce06a53c29f47e766928914dfeb42388a0a5b",
|
||||||
"defaultLocation": "IOLA",
|
"defaultLocation": "IOLA",
|
||||||
"permissions": {
|
|
||||||
"manage_users": false,
|
|
||||||
"view_reports": true,
|
|
||||||
"create_quotes": true,
|
|
||||||
"approve_quotes": false,
|
|
||||||
"manage_products": false,
|
|
||||||
"manage_inventory": false
|
|
||||||
},
|
|
||||||
"locationSettings": {
|
"locationSettings": {
|
||||||
"LINDS": {
|
"LINDS": {
|
||||||
"accessible": true
|
"accessible": true,
|
||||||
|
"permissions": {}
|
||||||
},
|
},
|
||||||
"IOLA": {
|
"IOLA": {
|
||||||
"accessible": true
|
"accessible": true,
|
||||||
},
|
"permissions": {}
|
||||||
"KC": {
|
|
||||||
"accessible": true
|
|
||||||
},
|
|
||||||
"BMD": {
|
|
||||||
"accessible": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"permissions": {
|
||||||
|
"manage_users": true,
|
||||||
|
"create_quotes": true
|
||||||
|
},
|
||||||
"active": true
|
"active": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"username": "Toni",
|
"username": "Toni",
|
||||||
"password": "pbkdf2:sha256:1000000$5k3daJKZt7hqNAkq$e77819a2b2ee4bf9955dc10b3b4da7b14de2b4ecda5ce5f553daf3783b0a316c",
|
"password": "pbkdf2:sha256:1000000$kf8fP8xO1ghC5Fig$e3b4155b7922a651e0680e73c984a48c7d4de2b02a43254d9080498c1071baca",
|
||||||
"defaultLocation": "LINDS",
|
"defaultLocation": "LINDS",
|
||||||
"locationSettings": {
|
"locationSettings": {
|
||||||
"LINDS": {
|
"LINDS": {
|
||||||
"accessible": true
|
"accessible": true,
|
||||||
},
|
"permissions": {}
|
||||||
"IOLA": {
|
|
||||||
"accessible": false
|
|
||||||
},
|
|
||||||
"KC": {
|
|
||||||
"accessible": false
|
|
||||||
},
|
|
||||||
"BMD": {
|
|
||||||
"accessible": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"permissions": {
|
||||||
|
"create_quotes": true
|
||||||
|
},
|
||||||
"active": true
|
"active": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Jason",
|
|
||||||
"password": "pbkdf2:sha256:1000000$rNSYLqRWZ6tdny1G$cad2afda9193b7cb50b9e9423f95ff83bc14428c8a2d98607cd6e5ce17cb70c7",
|
|
||||||
"defaultLocation": "KC",
|
|
||||||
"locationSettings": {
|
|
||||||
"LINDS": {
|
|
||||||
"accessible": true
|
|
||||||
},
|
|
||||||
"IOLA": {
|
|
||||||
"accessible": true
|
|
||||||
},
|
|
||||||
"KC": {
|
|
||||||
"accessible": true
|
|
||||||
},
|
|
||||||
"BMD": {
|
|
||||||
"accessible": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"active": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
"""
|
|
||||||
Debug startup script - writes detailed logs to file
|
|
||||||
Upload this and temporarily set it as the startup file to diagnose issues
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
# Create log file
|
|
||||||
log_file = '/home/bmdwtjuw/product-finder/startup_debug.log'
|
|
||||||
|
|
||||||
def log(message):
|
|
||||||
with open(log_file, 'a') as f:
|
|
||||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
f.write(f"[{timestamp}] {message}\n")
|
|
||||||
|
|
||||||
try:
|
|
||||||
log("="*70)
|
|
||||||
log("STARTUP DEBUG - BEGIN")
|
|
||||||
log("="*70)
|
|
||||||
|
|
||||||
# Python info
|
|
||||||
log(f"Python version: {sys.version}")
|
|
||||||
log(f"Python executable: {sys.executable}")
|
|
||||||
log(f"Current directory: {os.getcwd()}")
|
|
||||||
|
|
||||||
# Environment variables
|
|
||||||
log("\nEnvironment Variables:")
|
|
||||||
log(f" APPLICATION_ROOT: {os.environ.get('APPLICATION_ROOT', 'NOT SET')}")
|
|
||||||
log(f" FLASK_ENV: {os.environ.get('FLASK_ENV', 'NOT SET')}")
|
|
||||||
|
|
||||||
# Python path
|
|
||||||
log(f"\nPython path: {sys.path}")
|
|
||||||
|
|
||||||
# Check Flask installation
|
|
||||||
log("\nChecking Flask installation...")
|
|
||||||
try:
|
|
||||||
import flask
|
|
||||||
log(f" ✓ Flask version: {flask.__version__ if hasattr(flask, '__version__') else 'unknown'}")
|
|
||||||
except ImportError as e:
|
|
||||||
log(f" ✗ Flask NOT installed: {e}")
|
|
||||||
log(" RUN: pip install Flask==3.0.0 Werkzeug==3.0.1")
|
|
||||||
|
|
||||||
# Check Werkzeug
|
|
||||||
try:
|
|
||||||
import werkzeug
|
|
||||||
log(f" ✓ Werkzeug version: {werkzeug.__version__ if hasattr(werkzeug, '__version__') else 'unknown'}")
|
|
||||||
except ImportError as e:
|
|
||||||
log(f" ✗ Werkzeug NOT installed: {e}")
|
|
||||||
|
|
||||||
# Check if app.py exists
|
|
||||||
log("\nChecking files...")
|
|
||||||
app_py_path = os.path.join(os.path.dirname(__file__), 'app.py')
|
|
||||||
if os.path.exists(app_py_path):
|
|
||||||
log(f" ✓ app.py exists at {app_py_path}")
|
|
||||||
else:
|
|
||||||
log(f" ✗ app.py NOT FOUND at {app_py_path}")
|
|
||||||
|
|
||||||
# Try importing app
|
|
||||||
log("\nAttempting to import Flask app...")
|
|
||||||
try:
|
|
||||||
from app import app
|
|
||||||
log(f" ✓ Successfully imported Flask app")
|
|
||||||
log(f" APPLICATION_ROOT config: {app.config.get('APPLICATION_ROOT', 'NOT SET')}")
|
|
||||||
|
|
||||||
# Count routes
|
|
||||||
routes = list(app.url_map.iter_rules())
|
|
||||||
log(f" ✓ Registered routes: {len(routes)}")
|
|
||||||
|
|
||||||
# Create WSGI application
|
|
||||||
def application(environ, start_response):
|
|
||||||
log(f"\nRequest received: {environ.get('PATH_INFO', '/')}")
|
|
||||||
return app(environ, start_response)
|
|
||||||
|
|
||||||
log("\n✓ APPLICATION READY - Check site now")
|
|
||||||
log("="*70)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
log(f" ✗ Failed to import app:")
|
|
||||||
log(f" Error: {e}")
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
log(f"\nFull traceback:")
|
|
||||||
log(traceback.format_exc())
|
|
||||||
|
|
||||||
# Create error application
|
|
||||||
def application(environ, start_response):
|
|
||||||
status = '500 Internal Server Error'
|
|
||||||
output = f'Import Error - Check {log_file}\n\n{traceback.format_exc()}'.encode('utf-8')
|
|
||||||
response_headers = [('Content-type', 'text/plain'),
|
|
||||||
('Content-Length', str(len(output)))]
|
|
||||||
start_response(status, response_headers)
|
|
||||||
return [output]
|
|
||||||
|
|
||||||
log("="*70)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# Catch-all for any errors
|
|
||||||
import traceback
|
|
||||||
log(f"\nFATAL ERROR during startup:")
|
|
||||||
log(str(e))
|
|
||||||
log(traceback.format_exc())
|
|
||||||
log("="*70)
|
|
||||||
|
|
||||||
def application(environ, start_response):
|
|
||||||
status = '500 Internal Server Error'
|
|
||||||
output = f'Startup Error - Check {log_file}'.encode('utf-8')
|
|
||||||
response_headers = [('Content-type', 'text/plain'),
|
|
||||||
('Content-Length', str(len(output)))]
|
|
||||||
start_response(status, response_headers)
|
|
||||||
return [output]
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Fix Passenger Configuration
|
|
||||||
============================
|
|
||||||
This script renames the corrupted passenger_wsgi.py file so Passenger
|
|
||||||
will use the control panel settings instead.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("=" * 70)
|
|
||||||
print("FIXING PASSENGER CONFIGURATION")
|
|
||||||
print("=" * 70)
|
|
||||||
|
|
||||||
# Check if passenger_wsgi.py exists
|
|
||||||
if os.path.exists('passenger_wsgi.py'):
|
|
||||||
print("\nFound passenger_wsgi.py - this file is causing the problem")
|
|
||||||
print("Renaming it to passenger_wsgi.py.DISABLED...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.rename('passenger_wsgi.py', 'passenger_wsgi.py.DISABLED')
|
|
||||||
print("SUCCESS: passenger_wsgi.py renamed to passenger_wsgi.py.DISABLED")
|
|
||||||
print("\nPassenger will now use the control panel settings:")
|
|
||||||
print(" Application startup file: wsgi.py")
|
|
||||||
print(" Application Entry point: app")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: Could not rename file: {e}")
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
print("\npassenger_wsgi.py not found - already fixed or doesn't exist")
|
|
||||||
|
|
||||||
print("\n" + "=" * 70)
|
|
||||||
print("NEXT STEPS:")
|
|
||||||
print("=" * 70)
|
|
||||||
print("1. Restart your Python application in the control panel")
|
|
||||||
print("2. Visit: https://columbiawindows.com/product-finder/test")
|
|
||||||
print("3. If it works, you're done!")
|
|
||||||
print("=" * 70)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Fix Passenger lock file permissions
|
|
||||||
# Run this on the server to resolve "Can't acquire lock" error
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "FIXING PASSENGER LOCK FILE ISSUES"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Navigate to app directory
|
|
||||||
cd ~/product-finder
|
|
||||||
|
|
||||||
# Remove any stale lock files
|
|
||||||
echo "🧹 Cleaning stale lock files..."
|
|
||||||
if [ -d "tmp" ]; then
|
|
||||||
rm -rf tmp/*
|
|
||||||
echo " ✓ tmp directory cleaned"
|
|
||||||
else
|
|
||||||
echo " ⚠️ tmp directory doesn't exist - will create it"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create tmp directory with correct permissions
|
|
||||||
echo ""
|
|
||||||
echo "📁 Setting up tmp directory..."
|
|
||||||
mkdir -p tmp
|
|
||||||
chmod 755 tmp
|
|
||||||
echo " ✓ tmp directory created with 755 permissions"
|
|
||||||
|
|
||||||
# Create restart file
|
|
||||||
echo ""
|
|
||||||
echo "🔄 Restarting Passenger..."
|
|
||||||
touch tmp/restart.txt
|
|
||||||
chmod 644 tmp/restart.txt
|
|
||||||
echo " ✓ tmp/restart.txt created"
|
|
||||||
|
|
||||||
# Set correct permissions for app files
|
|
||||||
echo ""
|
|
||||||
echo "🔒 Setting file permissions..."
|
|
||||||
chmod 755 .
|
|
||||||
chmod 644 passenger_wsgi.py
|
|
||||||
chmod 644 app.py
|
|
||||||
chmod 644 .htaccess
|
|
||||||
chmod -R 755 data 2>/dev/null || mkdir -p data && chmod 755 data
|
|
||||||
chmod -R 755 templates
|
|
||||||
chmod -R 755 static 2>/dev/null || true
|
|
||||||
echo " ✓ File permissions set"
|
|
||||||
|
|
||||||
# Show current tmp directory status
|
|
||||||
echo ""
|
|
||||||
echo "📊 Current tmp directory status:"
|
|
||||||
ls -la tmp/
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "✓ LOCK FILE ISSUE FIXED"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
echo "Next steps:"
|
|
||||||
echo "1. Wait 30 seconds for Passenger to restart"
|
|
||||||
echo "2. Test: https://columbiawindows.com/product-finder/"
|
|
||||||
echo ""
|
|
||||||
echo "If still failing, check error log:"
|
|
||||||
echo "tail -50 ~/logs/error_log"
|
|
||||||
echo ""
|
|
||||||
@@ -1,295 +0,0 @@
|
|||||||
"""
|
|
||||||
Generate Bitwise Helper File for Product Classification
|
|
||||||
Creates a helper file with bitwise flags based on product attributes (columns G-S)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import csv
|
|
||||||
import json
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
# Bit position definitions
|
|
||||||
BIT_DEFINITIONS = {
|
|
||||||
# Base Type (Bits 0-1)
|
|
||||||
'base_door': 0, # 2^0 = 1
|
|
||||||
'base_window': 1, # 2^1 = 2
|
|
||||||
|
|
||||||
# Product Flags (Bits 2-3)
|
|
||||||
'is_accessory': 2, # 2^2 = 4
|
|
||||||
'specific_item': 3, # 2^3 = 8
|
|
||||||
|
|
||||||
# Materials (Bits 4-5)
|
|
||||||
'material_aluminum': 4, # 2^4 = 16
|
|
||||||
'material_vinyl': 5, # 2^5 = 32
|
|
||||||
|
|
||||||
# Colors (Bits 6-11)
|
|
||||||
'color_black': 6, # 2^6 = 64
|
|
||||||
'color_white': 7, # 2^7 = 128
|
|
||||||
'color_bronze': 8, # 2^8 = 256
|
|
||||||
'color_tan': 9, # 2^9 = 512
|
|
||||||
'color_mill': 10, # 2^10 = 1024
|
|
||||||
'color_sandstone': 11, # 2^11 = 2048
|
|
||||||
}
|
|
||||||
|
|
||||||
# Sub-type bits will be assigned dynamically (starting at bit 12)
|
|
||||||
SUBTYPE_BIT_START = 12
|
|
||||||
|
|
||||||
def parse_bool(value):
|
|
||||||
"""Parse TRUE/FALSE string to boolean"""
|
|
||||||
if isinstance(value, str):
|
|
||||||
return value.strip().upper() == 'TRUE'
|
|
||||||
return bool(value)
|
|
||||||
|
|
||||||
def calculate_bit_value(row, subtype_map):
|
|
||||||
"""Calculate the bitwise value for a product row"""
|
|
||||||
bit_value = 0
|
|
||||||
explanation = []
|
|
||||||
|
|
||||||
# Base Type (Column G - index 6)
|
|
||||||
base_type = row[6].strip() if len(row) > 6 and row[6] else ""
|
|
||||||
if base_type.lower() == 'door':
|
|
||||||
bit_value |= (1 << BIT_DEFINITIONS['base_door'])
|
|
||||||
explanation.append('Door')
|
|
||||||
elif base_type.lower() == 'window':
|
|
||||||
bit_value |= (1 << BIT_DEFINITIONS['base_window'])
|
|
||||||
explanation.append('Window')
|
|
||||||
|
|
||||||
# Accessory Flag (Column J - index 9)
|
|
||||||
if len(row) > 9 and parse_bool(row[9]):
|
|
||||||
bit_value |= (1 << BIT_DEFINITIONS['is_accessory'])
|
|
||||||
explanation.append('Accessory')
|
|
||||||
|
|
||||||
# Specific Item Link (Column K - index 10)
|
|
||||||
if len(row) > 10 and parse_bool(row[10]):
|
|
||||||
bit_value |= (1 << BIT_DEFINITIONS['specific_item'])
|
|
||||||
explanation.append('SpecificItem')
|
|
||||||
|
|
||||||
# Materials
|
|
||||||
if len(row) > 11 and parse_bool(row[11]): # Aluminum (Column L)
|
|
||||||
bit_value |= (1 << BIT_DEFINITIONS['material_aluminum'])
|
|
||||||
explanation.append('Aluminum')
|
|
||||||
|
|
||||||
if len(row) > 12 and parse_bool(row[12]): # Vinyl (Column M)
|
|
||||||
bit_value |= (1 << BIT_DEFINITIONS['material_vinyl'])
|
|
||||||
explanation.append('Vinyl')
|
|
||||||
|
|
||||||
# Colors
|
|
||||||
color_columns = [
|
|
||||||
(13, 'color_black', 'Black'),
|
|
||||||
(14, 'color_white', 'White'),
|
|
||||||
(15, 'color_bronze', 'Bronze'),
|
|
||||||
(16, 'color_tan', 'Tan'),
|
|
||||||
(17, 'color_mill', 'Mill'),
|
|
||||||
(18, 'color_sandstone', 'Sandstone'),
|
|
||||||
]
|
|
||||||
|
|
||||||
for col_idx, bit_key, color_name in color_columns:
|
|
||||||
if len(row) > col_idx and parse_bool(row[col_idx]):
|
|
||||||
bit_value |= (1 << BIT_DEFINITIONS[bit_key])
|
|
||||||
explanation.append(color_name)
|
|
||||||
|
|
||||||
# Sub-types (Columns H and I - indices 7 and 8)
|
|
||||||
door_subtype = row[7].strip() if len(row) > 7 and row[7] else ""
|
|
||||||
window_subtype = row[8].strip() if len(row) > 8 and row[8] else ""
|
|
||||||
|
|
||||||
if door_subtype and door_subtype in subtype_map:
|
|
||||||
bit_pos = subtype_map[door_subtype]
|
|
||||||
bit_value |= (1 << bit_pos)
|
|
||||||
explanation.append(f'Subtype:{door_subtype}')
|
|
||||||
|
|
||||||
if window_subtype and window_subtype in subtype_map:
|
|
||||||
bit_pos = subtype_map[window_subtype]
|
|
||||||
bit_value |= (1 << bit_pos)
|
|
||||||
explanation.append(f'Subtype:{window_subtype}')
|
|
||||||
|
|
||||||
return bit_value, explanation
|
|
||||||
|
|
||||||
def collect_subtypes(csv_path):
|
|
||||||
"""Collect all unique sub-types from the CSV"""
|
|
||||||
subtypes = set()
|
|
||||||
|
|
||||||
with open(csv_path, 'r', encoding='utf-8') as f:
|
|
||||||
reader = csv.reader(f)
|
|
||||||
next(reader) # Skip first header row
|
|
||||||
next(reader) # Skip second header row
|
|
||||||
|
|
||||||
for row in reader:
|
|
||||||
if len(row) < 8:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Skip discontinued items
|
|
||||||
if len(row) > 0 and parse_bool(row[0]):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Collect door subtype (column H - index 7)
|
|
||||||
if len(row) > 7 and row[7].strip():
|
|
||||||
subtypes.add(row[7].strip())
|
|
||||||
|
|
||||||
# Collect window subtype (column I - index 8)
|
|
||||||
if len(row) > 8 and row[8].strip():
|
|
||||||
subtypes.add(row[8].strip())
|
|
||||||
|
|
||||||
return sorted(subtypes)
|
|
||||||
|
|
||||||
def generate_helper_files(csv_path):
|
|
||||||
"""Generate helper CSV and JSON files with bitwise values"""
|
|
||||||
|
|
||||||
print("Analyzing product data...")
|
|
||||||
|
|
||||||
# First pass: collect all unique sub-types
|
|
||||||
subtypes = collect_subtypes(csv_path)
|
|
||||||
|
|
||||||
# Create subtype bit mapping
|
|
||||||
subtype_map = {}
|
|
||||||
for idx, subtype in enumerate(subtypes):
|
|
||||||
subtype_map[subtype] = SUBTYPE_BIT_START + idx
|
|
||||||
|
|
||||||
print(f"Found {len(subtypes)} unique sub-types")
|
|
||||||
|
|
||||||
# Second pass: calculate bit values
|
|
||||||
results = []
|
|
||||||
|
|
||||||
with open(csv_path, 'r', encoding='utf-8') as f:
|
|
||||||
reader = csv.reader(f)
|
|
||||||
next(reader) # Skip first header row
|
|
||||||
next(reader) # Skip second header row
|
|
||||||
|
|
||||||
for row in reader:
|
|
||||||
if len(row) < 3:
|
|
||||||
continue
|
|
||||||
|
|
||||||
prod_code = row[2].strip() # Column C - PROD_CODE
|
|
||||||
discontinued = parse_bool(row[0]) # Column A - DISCONT
|
|
||||||
|
|
||||||
bit_value, explanation = calculate_bit_value(row, subtype_map)
|
|
||||||
|
|
||||||
results.append({
|
|
||||||
'PROD_CODE': prod_code,
|
|
||||||
'BIT_VALUE': bit_value,
|
|
||||||
'BIT_HEX': f"0x{bit_value:X}",
|
|
||||||
'BIT_BINARY': f"{bit_value:b}",
|
|
||||||
'DISCONTINUED': discontinued,
|
|
||||||
'FLAGS': ' | '.join(explanation) if explanation else 'None'
|
|
||||||
})
|
|
||||||
|
|
||||||
# Filter out discontinued products for output files
|
|
||||||
active_results = [r for r in results if not r['DISCONTINUED']]
|
|
||||||
|
|
||||||
print(f"Total products processed: {len(results)}")
|
|
||||||
print(f"Active products: {len(active_results)}")
|
|
||||||
print(f"Discontinued products (filtered out): {len(results) - len(active_results)}")
|
|
||||||
|
|
||||||
# Write CSV helper file (only active products)
|
|
||||||
csv_output = 'data/product_bitwise.csv'
|
|
||||||
with open(csv_output, 'w', newline='', encoding='utf-8') as f:
|
|
||||||
writer = csv.DictWriter(f, fieldnames=['PROD_CODE', 'BIT_VALUE', 'BIT_HEX', 'BIT_BINARY', 'DISCONTINUED', 'FLAGS'])
|
|
||||||
writer.writeheader()
|
|
||||||
writer.writerows(active_results)
|
|
||||||
|
|
||||||
print(f"✓ Created {csv_output}")
|
|
||||||
|
|
||||||
# Write JSON helper file (only active products)
|
|
||||||
json_output = 'data/product_bitwise.json'
|
|
||||||
with open(json_output, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(active_results, f, indent=2)
|
|
||||||
|
|
||||||
print(f"✓ Created {json_output}")
|
|
||||||
|
|
||||||
# Create bit legend/documentation
|
|
||||||
legend = {
|
|
||||||
'description': 'Bitwise flag system for product classification',
|
|
||||||
'bit_definitions': {
|
|
||||||
'base_attributes': {
|
|
||||||
'bit_0 (1)': 'Base Type = Door',
|
|
||||||
'bit_1 (2)': 'Base Type = Window',
|
|
||||||
'bit_2 (4)': 'Is Accessory',
|
|
||||||
'bit_3 (8)': 'Specific Item Link',
|
|
||||||
},
|
|
||||||
'materials': {
|
|
||||||
'bit_4 (16)': 'Material = Aluminum',
|
|
||||||
'bit_5 (32)': 'Material = Vinyl',
|
|
||||||
},
|
|
||||||
'colors': {
|
|
||||||
'bit_6 (64)': 'Color = Black',
|
|
||||||
'bit_7 (128)': 'Color = White',
|
|
||||||
'bit_8 (256)': 'Color = Bronze',
|
|
||||||
'bit_9 (512)': 'Color = Tan',
|
|
||||||
'bit_10 (1024)': 'Color = Mill',
|
|
||||||
'bit_11 (2048)': 'Color = Sandstone',
|
|
||||||
},
|
|
||||||
'subtypes': {}
|
|
||||||
},
|
|
||||||
'usage_examples': {
|
|
||||||
'check_if_door': 'bit_value & 1',
|
|
||||||
'check_if_window': 'bit_value & 2',
|
|
||||||
'check_if_aluminum': 'bit_value & 16',
|
|
||||||
'check_if_white': 'bit_value & 128',
|
|
||||||
'check_multiple': '(bit_value & 1) and (bit_value & 16) # Door AND Aluminum',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add subtype mappings to legend
|
|
||||||
for subtype, bit_pos in sorted(subtype_map.items(), key=lambda x: x[1]):
|
|
||||||
bit_value = 1 << bit_pos
|
|
||||||
legend['bit_definitions']['subtypes'][f'bit_{bit_pos} ({bit_value})'] = f'Subtype = {subtype}'
|
|
||||||
legend['usage_examples'][f'check_subtype_{subtype.replace(" ", "_").lower()}'] = f'bit_value & {bit_value}'
|
|
||||||
|
|
||||||
# Write legend file
|
|
||||||
legend_output = 'data/bitwise_legend.json'
|
|
||||||
with open(legend_output, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(legend, f, indent=2)
|
|
||||||
|
|
||||||
print(f"✓ Created {legend_output}")
|
|
||||||
|
|
||||||
# Generate statistics
|
|
||||||
stats = {
|
|
||||||
'total_products': len([r for r in results if not r['DISCONTINUED']]),
|
|
||||||
'total_discontinued': len([r for r in results if r['DISCONTINUED']]),
|
|
||||||
'total_accessories': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 4)]),
|
|
||||||
'doors': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 1)]),
|
|
||||||
'windows': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 2)]),
|
|
||||||
'aluminum_products': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 16)]),
|
|
||||||
'vinyl_products': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 32)]),
|
|
||||||
'multi_material': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 16) and (r['BIT_VALUE'] & 32)]),
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n" + "="*60)
|
|
||||||
print("STATISTICS")
|
|
||||||
print("="*60)
|
|
||||||
print(f"Total Active Products: {stats['total_products']}")
|
|
||||||
print(f"Total Discontinued: {stats['total_discontinued']}")
|
|
||||||
print(f"Accessories: {stats['total_accessories']}")
|
|
||||||
print(f"Doors: {stats['doors']}")
|
|
||||||
print(f"Windows: {stats['windows']}")
|
|
||||||
print(f"Aluminum Products: {stats['aluminum_products']}")
|
|
||||||
print(f"Vinyl Products: {stats['vinyl_products']}")
|
|
||||||
print(f"Multi-Material Products: {stats['multi_material']}")
|
|
||||||
print(f"Unique Sub-types: {len(subtypes)}")
|
|
||||||
print("="*60)
|
|
||||||
|
|
||||||
# Show example products
|
|
||||||
print("\nEXAMPLE PRODUCTS:")
|
|
||||||
print("-"*60)
|
|
||||||
for i, result in enumerate(active_results[:5]):
|
|
||||||
print(f"{result['PROD_CODE']:12} | {result['BIT_VALUE']:6} | {result['BIT_HEX']:8} | {result['FLAGS']}")
|
|
||||||
print("-"*60)
|
|
||||||
|
|
||||||
return active_results, legend, subtype_map
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
csv_path = 'data/products.csv'
|
|
||||||
|
|
||||||
try:
|
|
||||||
results, legend, subtype_map = generate_helper_files(csv_path)
|
|
||||||
print("\n✓ All helper files generated successfully!")
|
|
||||||
print("\nGenerated files:")
|
|
||||||
print(" - data/product_bitwise.csv (CSV format with bit values)")
|
|
||||||
print(" - data/product_bitwise.json (JSON format with bit values)")
|
|
||||||
print(" - data/bitwise_legend.json (Bit mapping documentation)")
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
print(f"Error: Could not find {csv_path}")
|
|
||||||
print("Make sure products.csv exists in the data/ folder")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
@@ -1,297 +0,0 @@
|
|||||||
"""
|
|
||||||
Dynamic Product Image Generator
|
|
||||||
Generates product images with configurable colors, hardware positions, and other options.
|
|
||||||
Uses PIL/Pillow for image manipulation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import hashlib
|
|
||||||
from io import BytesIO
|
|
||||||
from PIL import Image, ImageDraw, ImageEnhance, ImageColor
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
class ProductImageGenerator:
|
|
||||||
"""Handles dynamic generation of product images based on configuration."""
|
|
||||||
|
|
||||||
def __init__(self, config_file='data/image_configs.json'):
|
|
||||||
"""Initialize with configuration file."""
|
|
||||||
self.config_file = config_file
|
|
||||||
self.configs = self._load_configs()
|
|
||||||
self.cache_dir = Path('cache/product_images')
|
|
||||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
def _load_configs(self):
|
|
||||||
"""Load image configuration from JSON file."""
|
|
||||||
try:
|
|
||||||
with open(self.config_file, 'r') as f:
|
|
||||||
return json.load(f)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error loading image configs: {e}")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def _find_image(self, image_path):
|
|
||||||
"""
|
|
||||||
Find image file, trying different extensions if needed.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image_path: Path to image (e.g., 'images/cobrai.jpg')
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Actual path to image file, or None if not found
|
|
||||||
"""
|
|
||||||
# Try the exact path first
|
|
||||||
if os.path.exists(image_path):
|
|
||||||
return image_path
|
|
||||||
|
|
||||||
# Get base path without extension
|
|
||||||
base_path = os.path.splitext(image_path)[0]
|
|
||||||
|
|
||||||
# Try common image extensions
|
|
||||||
for ext in ['.png', '.jpg', '.jpeg', '.PNG', '.JPG', '.JPEG']:
|
|
||||||
test_path = base_path + ext
|
|
||||||
if os.path.exists(test_path):
|
|
||||||
return test_path
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def generate_product_image(self, product_code, color='white', hinge='right',
|
|
||||||
material='aluminum', use_cache=True):
|
|
||||||
"""
|
|
||||||
Generate a product image with specified configuration.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
product_code: Product identifier (e.g., 'cobrai')
|
|
||||||
color: Color name (e.g., 'white', 'black', 'bronze')
|
|
||||||
hinge: Hinge side ('right' or 'left')
|
|
||||||
material: Material type (for future use)
|
|
||||||
use_cache: Whether to use cached images
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PIL Image object
|
|
||||||
"""
|
|
||||||
product_code = product_code.lower()
|
|
||||||
|
|
||||||
# Check if product config exists
|
|
||||||
if product_code not in self.configs:
|
|
||||||
raise ValueError(f"Product {product_code} not found in configuration")
|
|
||||||
|
|
||||||
config = self.configs[product_code]
|
|
||||||
|
|
||||||
# Check cache first
|
|
||||||
if use_cache:
|
|
||||||
cache_key = self._get_cache_key(product_code, color, hinge, material)
|
|
||||||
cached_image = self._get_cached_image(cache_key)
|
|
||||||
if cached_image:
|
|
||||||
return cached_image
|
|
||||||
|
|
||||||
# Load base image
|
|
||||||
base_image_path = config['sourceImage']
|
|
||||||
|
|
||||||
# Try to find the image with different extensions if needed
|
|
||||||
image_path = self._find_image(base_image_path)
|
|
||||||
|
|
||||||
if not image_path:
|
|
||||||
raise FileNotFoundError(f"Could not load base image: {base_image_path}. Tried .jpg, .png, .jpeg")
|
|
||||||
|
|
||||||
try:
|
|
||||||
base_image = Image.open(image_path).convert('RGBA')
|
|
||||||
except Exception as e:
|
|
||||||
raise FileNotFoundError(f"Could not load base image: {image_path}. Error: {e}")
|
|
||||||
|
|
||||||
# Apply color to regions
|
|
||||||
if color in config['availableColors']:
|
|
||||||
color_rgb = tuple(config['availableColors'][color]['rgb'])
|
|
||||||
base_image = self._apply_color_to_regions(
|
|
||||||
base_image,
|
|
||||||
config['colorableRegions'],
|
|
||||||
color_rgb
|
|
||||||
)
|
|
||||||
|
|
||||||
# Apply hardware (handles, locks)
|
|
||||||
if hinge in config.get('hardwarePositions', {}):
|
|
||||||
base_image = self._apply_hardware(
|
|
||||||
base_image,
|
|
||||||
config['hardwarePositions'],
|
|
||||||
hinge
|
|
||||||
)
|
|
||||||
|
|
||||||
# Flip image if left hinge
|
|
||||||
if hinge == 'left':
|
|
||||||
base_image = base_image.transpose(Image.FLIP_LEFT_RIGHT)
|
|
||||||
|
|
||||||
# Cache the result
|
|
||||||
if use_cache:
|
|
||||||
self._cache_image(cache_key, base_image)
|
|
||||||
|
|
||||||
return base_image
|
|
||||||
|
|
||||||
def _apply_color_to_regions(self, image, regions, target_color):
|
|
||||||
"""
|
|
||||||
Apply color to specific regions of the image.
|
|
||||||
|
|
||||||
This method attempts to recolor the frame/panel areas while preserving
|
|
||||||
shadows, highlights, and texture.
|
|
||||||
"""
|
|
||||||
img_copy = image.copy()
|
|
||||||
pixels = img_copy.load()
|
|
||||||
width, height = img_copy.size
|
|
||||||
|
|
||||||
# Get the average brightness of the target color
|
|
||||||
target_brightness = sum(target_color) / 3
|
|
||||||
|
|
||||||
for region in regions:
|
|
||||||
x1, y1 = region['topLeft']
|
|
||||||
x2, y2 = region['bottomRight']
|
|
||||||
|
|
||||||
# Ensure coordinates are within bounds
|
|
||||||
x1 = max(0, min(x1, width - 1))
|
|
||||||
x2 = max(0, min(x2, width))
|
|
||||||
y1 = max(0, min(y1, height - 1))
|
|
||||||
y2 = max(0, min(y2, height))
|
|
||||||
|
|
||||||
# Apply color to region while preserving luminosity
|
|
||||||
for y in range(y1, y2):
|
|
||||||
for x in range(x1, x2):
|
|
||||||
try:
|
|
||||||
r, g, b, a = pixels[x, y]
|
|
||||||
|
|
||||||
# Calculate original brightness
|
|
||||||
original_brightness = (r + g + b) / 3
|
|
||||||
|
|
||||||
# Preserve relative brightness
|
|
||||||
if original_brightness > 0:
|
|
||||||
brightness_factor = original_brightness / 255.0
|
|
||||||
|
|
||||||
# Apply target color with brightness preservation
|
|
||||||
new_r = int(target_color[0] * brightness_factor)
|
|
||||||
new_g = int(target_color[1] * brightness_factor)
|
|
||||||
new_b = int(target_color[2] * brightness_factor)
|
|
||||||
|
|
||||||
# Ensure values are in valid range
|
|
||||||
new_r = max(0, min(255, new_r))
|
|
||||||
new_g = max(0, min(255, new_g))
|
|
||||||
new_b = max(0, min(255, new_b))
|
|
||||||
|
|
||||||
pixels[x, y] = (new_r, new_g, new_b, a)
|
|
||||||
except IndexError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
return img_copy
|
|
||||||
|
|
||||||
def _apply_hardware(self, image, hardware_positions, hinge_side):
|
|
||||||
"""Apply hardware (handles, locks) to the image."""
|
|
||||||
img_copy = image.copy()
|
|
||||||
|
|
||||||
hardware_key = f"handle_{hinge_side}"
|
|
||||||
if hardware_key in hardware_positions:
|
|
||||||
hardware_config = hardware_positions[hardware_key]
|
|
||||||
hardware_path = hardware_config.get('image')
|
|
||||||
|
|
||||||
if hardware_path:
|
|
||||||
# Try to find the hardware image with different extensions
|
|
||||||
actual_path = self._find_image(hardware_path)
|
|
||||||
|
|
||||||
if actual_path:
|
|
||||||
try:
|
|
||||||
hardware_img = Image.open(actual_path).convert('RGBA')
|
|
||||||
|
|
||||||
# Flip if needed
|
|
||||||
if hardware_config.get('flip', False):
|
|
||||||
hardware_img = hardware_img.transpose(Image.FLIP_LEFT_RIGHT)
|
|
||||||
|
|
||||||
# Paste hardware at specified position
|
|
||||||
x, y = hardware_config['x'], hardware_config['y']
|
|
||||||
img_copy.paste(hardware_img, (x, y), hardware_img)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Could not apply hardware: {e}")
|
|
||||||
|
|
||||||
return img_copy
|
|
||||||
|
|
||||||
def _get_cache_key(self, product_code, color, hinge, material):
|
|
||||||
"""Generate a unique cache key for the configuration."""
|
|
||||||
key_string = f"{product_code}_{color}_{hinge}_{material}"
|
|
||||||
return hashlib.md5(key_string.encode()).hexdigest()
|
|
||||||
|
|
||||||
def _get_cached_image(self, cache_key):
|
|
||||||
"""Retrieve cached image if available."""
|
|
||||||
cache_path = self.cache_dir / f"{cache_key}.png"
|
|
||||||
if cache_path.exists():
|
|
||||||
try:
|
|
||||||
return Image.open(cache_path)
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _cache_image(self, cache_key, image):
|
|
||||||
"""Save image to cache."""
|
|
||||||
cache_path = self.cache_dir / f"{cache_key}.png"
|
|
||||||
try:
|
|
||||||
image.save(cache_path, 'PNG', optimize=True)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Could not cache image: {e}")
|
|
||||||
|
|
||||||
def get_product_config(self, product_code):
|
|
||||||
"""Get configuration for a specific product."""
|
|
||||||
return self.configs.get(product_code.lower())
|
|
||||||
|
|
||||||
def clear_cache(self, product_code=None):
|
|
||||||
"""Clear cached images. If product_code provided, only clear that product's cache."""
|
|
||||||
if product_code:
|
|
||||||
# Clear specific product cache (would need to track cache keys)
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Clear all cache
|
|
||||||
for cache_file in self.cache_dir.glob('*.png'):
|
|
||||||
try:
|
|
||||||
cache_file.unlink()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def recolor_image_advanced(image, source_color_range, target_color):
|
|
||||||
"""
|
|
||||||
Advanced recoloring that replaces pixels within a color range.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image: PIL Image
|
|
||||||
source_color_range: Dict with 'min' and 'max' RGB tuples
|
|
||||||
target_color: Target RGB tuple
|
|
||||||
"""
|
|
||||||
img_copy = image.copy()
|
|
||||||
pixels = img_copy.load()
|
|
||||||
width, height = img_copy.size
|
|
||||||
|
|
||||||
min_r, min_g, min_b = source_color_range['min']
|
|
||||||
max_r, max_g, max_b = source_color_range['max']
|
|
||||||
|
|
||||||
for y in range(height):
|
|
||||||
for x in range(width):
|
|
||||||
r, g, b, a = pixels[x, y]
|
|
||||||
|
|
||||||
# Check if pixel is within source color range
|
|
||||||
if (min_r <= r <= max_r and
|
|
||||||
min_g <= g <= max_g and
|
|
||||||
min_b <= b <= max_b):
|
|
||||||
|
|
||||||
# Calculate brightness factor
|
|
||||||
brightness = (r + g + b) / 3 / 255.0
|
|
||||||
|
|
||||||
# Apply target color with brightness
|
|
||||||
new_r = int(target_color[0] * brightness)
|
|
||||||
new_g = int(target_color[1] * brightness)
|
|
||||||
new_b = int(target_color[2] * brightness)
|
|
||||||
|
|
||||||
pixels[x, y] = (new_r, new_g, new_b, a)
|
|
||||||
|
|
||||||
return img_copy
|
|
||||||
|
|
||||||
|
|
||||||
def image_to_base64(image, format='PNG'):
|
|
||||||
"""Convert PIL Image to base64 string."""
|
|
||||||
import base64
|
|
||||||
buffered = BytesIO()
|
|
||||||
image.save(buffered, format=format)
|
|
||||||
img_str = base64.b64encode(buffered.getvalue()).decode()
|
|
||||||
return f"data:image/{format.lower()};base64,{img_str}"
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 477 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 959 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 969 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 922 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.8 MiB |
@@ -1,451 +0,0 @@
|
|||||||
/**
|
|
||||||
* Product Encoding/Decoding Helper
|
|
||||||
*
|
|
||||||
* Provides functions to encode and decode product configurations into compact
|
|
||||||
* hex-based URL strings. Uses bit-packing for efficient, stable encoding.
|
|
||||||
*
|
|
||||||
* Format: v-T-C-M-HHHH-HHHH-HHHH
|
|
||||||
* See /planning/ENCODING_SYSTEM.md for detailed documentation
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// LOOKUP TABLES
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
const ENCODING_VERSION = 0;
|
|
||||||
|
|
||||||
// Main Product Attributes (single hex character, 0-15)
|
|
||||||
const PRODUCT_TYPE = {
|
|
||||||
'PATIO_DOOR': 1,
|
|
||||||
'STORM_DOOR': 3,
|
|
||||||
'STORM_WINDOW': 5,
|
|
||||||
'PRIMARY_WINDOW': 7
|
|
||||||
};
|
|
||||||
|
|
||||||
const PRODUCT_TYPE_REVERSE = {
|
|
||||||
1: 'PATIO_DOOR',
|
|
||||||
3: 'STORM_DOOR',
|
|
||||||
5: 'STORM_WINDOW',
|
|
||||||
7: 'PRIMARY_WINDOW'
|
|
||||||
};
|
|
||||||
|
|
||||||
const PRODUCT_COLOR = {
|
|
||||||
'BLACK': 1,
|
|
||||||
'BRONZE': 2,
|
|
||||||
'SANDSTONE': 3,
|
|
||||||
'WHITE': 4,
|
|
||||||
'TAN': 5,
|
|
||||||
'MILL': 6
|
|
||||||
};
|
|
||||||
|
|
||||||
const PRODUCT_COLOR_REVERSE = {
|
|
||||||
1: 'BLACK',
|
|
||||||
2: 'BRONZE',
|
|
||||||
3: 'SANDSTONE',
|
|
||||||
4: 'WHITE',
|
|
||||||
5: 'TAN',
|
|
||||||
6: 'MILL'
|
|
||||||
};
|
|
||||||
|
|
||||||
const PRODUCT_MATERIAL = {
|
|
||||||
'ALUMINUM': 1,
|
|
||||||
'VINYL': 2
|
|
||||||
};
|
|
||||||
|
|
||||||
const PRODUCT_MATERIAL_REVERSE = {
|
|
||||||
1: 'ALUMINUM',
|
|
||||||
2: 'VINYL'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hardware Fields (5 bits each, 0-31)
|
|
||||||
const HARDWARE_TYPE = {
|
|
||||||
'NONE': 0,
|
|
||||||
'LEVER': 1,
|
|
||||||
'PULL': 2,
|
|
||||||
'PULL_HANDLE': 3,
|
|
||||||
'DEADBOLT': 4,
|
|
||||||
'HINGE': 5
|
|
||||||
};
|
|
||||||
|
|
||||||
const HARDWARE_TYPE_REVERSE = {
|
|
||||||
0: 'NONE',
|
|
||||||
1: 'LEVER',
|
|
||||||
2: 'PULL',
|
|
||||||
3: 'PULL_HANDLE',
|
|
||||||
4: 'DEADBOLT',
|
|
||||||
5: 'HINGE'
|
|
||||||
};
|
|
||||||
|
|
||||||
const HARDWARE_STYLE = {
|
|
||||||
'NONE': 0,
|
|
||||||
'STANDARD': 1,
|
|
||||||
'PUSH': 2,
|
|
||||||
'ALTERNATIVE': 3,
|
|
||||||
'CONTEMPORARY': 4,
|
|
||||||
'TRADITIONAL': 5
|
|
||||||
};
|
|
||||||
|
|
||||||
const HARDWARE_STYLE_REVERSE = {
|
|
||||||
0: 'NONE',
|
|
||||||
1: 'STANDARD',
|
|
||||||
2: 'PUSH',
|
|
||||||
3: 'ALTERNATIVE',
|
|
||||||
4: 'CONTEMPORARY',
|
|
||||||
5: 'TRADITIONAL'
|
|
||||||
};
|
|
||||||
|
|
||||||
const HARDWARE_COLOR = {
|
|
||||||
'NONE': 0,
|
|
||||||
'BRASS': 1,
|
|
||||||
'WHITE': 2,
|
|
||||||
'BLACK': 3,
|
|
||||||
'SATIN': 4,
|
|
||||||
'NICKEL': 5,
|
|
||||||
'BRONZE': 6
|
|
||||||
};
|
|
||||||
|
|
||||||
const HARDWARE_COLOR_REVERSE = {
|
|
||||||
0: 'NONE',
|
|
||||||
1: 'BRASS',
|
|
||||||
2: 'WHITE',
|
|
||||||
3: 'BLACK',
|
|
||||||
4: 'SATIN',
|
|
||||||
5: 'NICKEL',
|
|
||||||
6: 'BRONZE'
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// ENCODING FUNCTIONS
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode a hardware configuration into a 4-character hex string
|
|
||||||
* Uses 5 bits per field (32 values each)
|
|
||||||
*
|
|
||||||
* @param {number} type - Hardware type (0-31)
|
|
||||||
* @param {number} style - Hardware style (0-31)
|
|
||||||
* @param {number} color - Hardware color (0-31)
|
|
||||||
* @returns {string} 4-character hex string (e.g., "0421")
|
|
||||||
*/
|
|
||||||
function encodeHardware(type, style, color) {
|
|
||||||
// Validate inputs
|
|
||||||
if (type < 0 || type > 31) throw new Error(`Invalid hardware type: ${type} (must be 0-31)`);
|
|
||||||
if (style < 0 || style > 31) throw new Error(`Invalid hardware style: ${style} (must be 0-31)`);
|
|
||||||
if (color < 0 || color > 31) throw new Error(`Invalid hardware color: ${color} (must be 0-31)`);
|
|
||||||
|
|
||||||
// Pack into 16 bits: [reserved(1)][type(5)][style(5)][color(5)]
|
|
||||||
const value = ((type & 0x1F) << 10) | // Bits 10-14
|
|
||||||
((style & 0x1F) << 5) | // Bits 5-9
|
|
||||||
(color & 0x1F); // Bits 0-4
|
|
||||||
|
|
||||||
return value.toString(16).toUpperCase().padStart(4, '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode a complete product configuration into a URL string
|
|
||||||
*
|
|
||||||
* @param {Object} config - Product configuration
|
|
||||||
* @param {number} config.type - Product type (0-15)
|
|
||||||
* @param {number} config.color - Product color (0-15)
|
|
||||||
* @param {number} config.material - Product material (0-15)
|
|
||||||
* @param {Array<Object>} config.hardware - Array of hardware items (optional)
|
|
||||||
* @param {number} config.hardware[].type - Hardware type (0-31)
|
|
||||||
* @param {number} config.hardware[].style - Hardware style (0-31)
|
|
||||||
* @param {number} config.hardware[].color - Hardware color (0-31)
|
|
||||||
* @returns {string} Encoded URL string (e.g., "0-1-4-1-0421")
|
|
||||||
*/
|
|
||||||
function encodeProduct(config) {
|
|
||||||
// Validate main attributes
|
|
||||||
if (config.type < 0 || config.type > 15) {
|
|
||||||
throw new Error(`Invalid product type: ${config.type} (must be 0-15)`);
|
|
||||||
}
|
|
||||||
if (config.color < 0 || config.color > 15) {
|
|
||||||
throw new Error(`Invalid product color: ${config.color} (must be 0-15)`);
|
|
||||||
}
|
|
||||||
if (config.material < 0 || config.material > 15) {
|
|
||||||
throw new Error(`Invalid product material: ${config.material} (must be 0-15)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build base string: version-type-color-material
|
|
||||||
const parts = [
|
|
||||||
ENCODING_VERSION.toString(16).toUpperCase(),
|
|
||||||
config.type.toString(16).toUpperCase(),
|
|
||||||
config.color.toString(16).toUpperCase(),
|
|
||||||
config.material.toString(16).toUpperCase()
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add hardware items if present
|
|
||||||
if (config.hardware && config.hardware.length > 0) {
|
|
||||||
config.hardware.forEach(hw => {
|
|
||||||
parts.push(encodeHardware(hw.type, hw.style, hw.color));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.join('-');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode using named constants (convenience function)
|
|
||||||
*
|
|
||||||
* @param {Object} config - Product configuration with named values
|
|
||||||
* @param {string} config.type - Product type name (e.g., 'PATIO_DOOR')
|
|
||||||
* @param {string} config.color - Color name (e.g., 'WHITE')
|
|
||||||
* @param {string} config.material - Material name (e.g., 'ALUMINUM')
|
|
||||||
* @param {Array<Object>} config.hardware - Array of hardware items (optional)
|
|
||||||
* @returns {string} Encoded URL string
|
|
||||||
*/
|
|
||||||
function encodeProductByName(config) {
|
|
||||||
const numericConfig = {
|
|
||||||
type: PRODUCT_TYPE[config.type],
|
|
||||||
color: PRODUCT_COLOR[config.color],
|
|
||||||
material: PRODUCT_MATERIAL[config.material],
|
|
||||||
hardware: []
|
|
||||||
};
|
|
||||||
|
|
||||||
if (config.hardware && config.hardware.length > 0) {
|
|
||||||
numericConfig.hardware = config.hardware.map(hw => ({
|
|
||||||
type: HARDWARE_TYPE[hw.type],
|
|
||||||
style: HARDWARE_STYLE[hw.style],
|
|
||||||
color: HARDWARE_COLOR[hw.color]
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeProduct(numericConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// DECODING FUNCTIONS
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a hardware hex string back to its components
|
|
||||||
*
|
|
||||||
* @param {string} hex - 4-character hex string (e.g., "0421")
|
|
||||||
* @returns {Object} Decoded hardware configuration
|
|
||||||
*/
|
|
||||||
function decodeHardware(hex) {
|
|
||||||
if (typeof hex !== 'string' || hex.length !== 4) {
|
|
||||||
throw new Error(`Invalid hardware hex string: ${hex} (must be 4 characters)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = parseInt(hex, 16);
|
|
||||||
|
|
||||||
if (isNaN(value)) {
|
|
||||||
throw new Error(`Invalid hex value: ${hex}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: (value >> 10) & 0x1F, // Extract bits 10-14
|
|
||||||
style: (value >> 5) & 0x1F, // Extract bits 5-9
|
|
||||||
color: value & 0x1F // Extract bits 0-4
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a complete product URL string
|
|
||||||
*
|
|
||||||
* @param {string} encodedString - Encoded URL string (e.g., "0-1-4-1-0421")
|
|
||||||
* @returns {Object} Decoded product configuration
|
|
||||||
*/
|
|
||||||
function decodeProduct(encodedString) {
|
|
||||||
if (typeof encodedString !== 'string') {
|
|
||||||
throw new Error('Invalid encoded string: must be a string');
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = encodedString.split('-');
|
|
||||||
|
|
||||||
if (parts.length < 4) {
|
|
||||||
throw new Error(`Invalid encoded string: ${encodedString} (must have at least 4 parts)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse version
|
|
||||||
const version = parseInt(parts[0], 16);
|
|
||||||
if (version !== ENCODING_VERSION) {
|
|
||||||
throw new Error(`Unsupported encoding version: ${version} (current: ${ENCODING_VERSION})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse main attributes
|
|
||||||
const config = {
|
|
||||||
version: version,
|
|
||||||
type: parseInt(parts[1], 16),
|
|
||||||
color: parseInt(parts[2], 16),
|
|
||||||
material: parseInt(parts[3], 16),
|
|
||||||
hardware: []
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse hardware items (if present)
|
|
||||||
for (let i = 4; i < parts.length; i++) {
|
|
||||||
config.hardware.push(decodeHardware(parts[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode and return named values (convenience function)
|
|
||||||
*
|
|
||||||
* @param {string} encodedString - Encoded URL string
|
|
||||||
* @returns {Object} Decoded product with named values
|
|
||||||
*/
|
|
||||||
function decodeProductToNames(encodedString) {
|
|
||||||
const numeric = decodeProduct(encodedString);
|
|
||||||
|
|
||||||
return {
|
|
||||||
version: numeric.version,
|
|
||||||
type: PRODUCT_TYPE_REVERSE[numeric.type] || `UNKNOWN_${numeric.type}`,
|
|
||||||
color: PRODUCT_COLOR_REVERSE[numeric.color] || `UNKNOWN_${numeric.color}`,
|
|
||||||
material: PRODUCT_MATERIAL_REVERSE[numeric.material] || `UNKNOWN_${numeric.material}`,
|
|
||||||
hardware: numeric.hardware.map(hw => ({
|
|
||||||
type: HARDWARE_TYPE_REVERSE[hw.type] || `UNKNOWN_${hw.type}`,
|
|
||||||
style: HARDWARE_STYLE_REVERSE[hw.style] || `UNKNOWN_${hw.style}`,
|
|
||||||
color: HARDWARE_COLOR_REVERSE[hw.color] || `UNKNOWN_${hw.color}`
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// UTILITY FUNCTIONS
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the binary representation of a hex string (for debugging)
|
|
||||||
*
|
|
||||||
* @param {string} hex - Hex string
|
|
||||||
* @returns {string} Binary string with spaces every 4 bits
|
|
||||||
*/
|
|
||||||
function hexToBinary(hex) {
|
|
||||||
const value = parseInt(hex, 16);
|
|
||||||
const binary = value.toString(2).padStart(16, '0');
|
|
||||||
return binary.match(/.{1,4}/g).join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that a configuration uses only defined values
|
|
||||||
*
|
|
||||||
* @param {Object} config - Product configuration
|
|
||||||
* @returns {Object} Validation result with errors array
|
|
||||||
*/
|
|
||||||
function validateConfiguration(config) {
|
|
||||||
const errors = [];
|
|
||||||
|
|
||||||
if (!PRODUCT_TYPE_REVERSE[config.type]) {
|
|
||||||
errors.push(`Invalid product type: ${config.type}`);
|
|
||||||
}
|
|
||||||
if (!PRODUCT_COLOR_REVERSE[config.color]) {
|
|
||||||
errors.push(`Invalid product color: ${config.color}`);
|
|
||||||
}
|
|
||||||
if (!PRODUCT_MATERIAL_REVERSE[config.material]) {
|
|
||||||
errors.push(`Invalid product material: ${config.material}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.hardware) {
|
|
||||||
config.hardware.forEach((hw, index) => {
|
|
||||||
if (!HARDWARE_TYPE_REVERSE[hw.type]) {
|
|
||||||
errors.push(`Invalid hardware ${index + 1} type: ${hw.type}`);
|
|
||||||
}
|
|
||||||
if (!HARDWARE_STYLE_REVERSE[hw.style]) {
|
|
||||||
errors.push(`Invalid hardware ${index + 1} style: ${hw.style}`);
|
|
||||||
}
|
|
||||||
if (!HARDWARE_COLOR_REVERSE[hw.color]) {
|
|
||||||
errors.push(`Invalid hardware ${index + 1} color: ${hw.color}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valid: errors.length === 0,
|
|
||||||
errors: errors
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// EXPORTS (for Node.js / Module usage)
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
module.exports = {
|
|
||||||
// Constants
|
|
||||||
ENCODING_VERSION,
|
|
||||||
PRODUCT_TYPE,
|
|
||||||
PRODUCT_COLOR,
|
|
||||||
PRODUCT_MATERIAL,
|
|
||||||
HARDWARE_TYPE,
|
|
||||||
HARDWARE_STYLE,
|
|
||||||
HARDWARE_COLOR,
|
|
||||||
|
|
||||||
// Encoding functions
|
|
||||||
encodeHardware,
|
|
||||||
encodeProduct,
|
|
||||||
encodeProductByName,
|
|
||||||
|
|
||||||
// Decoding functions
|
|
||||||
decodeHardware,
|
|
||||||
decodeProduct,
|
|
||||||
decodeProductToNames,
|
|
||||||
|
|
||||||
// Utilities
|
|
||||||
hexToBinary,
|
|
||||||
validateConfiguration
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// EXAMPLE USAGE
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Example 1: Encode by numeric values
|
|
||||||
const encoded1 = encodeProduct({
|
|
||||||
type: 1, // Patio Door
|
|
||||||
color: 4, // White
|
|
||||||
material: 1, // Aluminum
|
|
||||||
hardware: [
|
|
||||||
{ type: 1, style: 1, color: 1 } // Lever, Standard, Brass
|
|
||||||
]
|
|
||||||
});
|
|
||||||
console.log(encoded1); // "0-1-4-1-0421"
|
|
||||||
|
|
||||||
// Example 2: Encode by named values
|
|
||||||
const encoded2 = encodeProductByName({
|
|
||||||
type: 'STORM_DOOR',
|
|
||||||
color: 'WHITE',
|
|
||||||
material: 'ALUMINUM',
|
|
||||||
hardware: [
|
|
||||||
{ type: 'LEVER', style: 'STANDARD', color: 'BRASS' },
|
|
||||||
{ type: 'PULL_HANDLE', style: 'ALTERNATIVE', color: 'SATIN' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
console.log(encoded2); // "0-3-4-1-0421-0C64"
|
|
||||||
|
|
||||||
// Example 3: Decode back to numeric values
|
|
||||||
const decoded1 = decodeProduct("0-1-4-1-0421");
|
|
||||||
console.log(decoded1);
|
|
||||||
// { version: 0, type: 1, color: 4, material: 1, hardware: [{ type: 1, style: 1, color: 1 }] }
|
|
||||||
|
|
||||||
// Example 4: Decode to named values
|
|
||||||
const decoded2 = decodeProductToNames("0-3-4-1-0421-0C64");
|
|
||||||
console.log(decoded2);
|
|
||||||
// {
|
|
||||||
// version: 0,
|
|
||||||
// type: 'STORM_DOOR',
|
|
||||||
// color: 'WHITE',
|
|
||||||
// material: 'ALUMINUM',
|
|
||||||
// hardware: [
|
|
||||||
// { type: 'LEVER', style: 'STANDARD', color: 'BRASS' },
|
|
||||||
// { type: 'PULL_HANDLE', style: 'ALTERNATIVE', color: 'SATIN' }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Example 5: Debug hardware encoding
|
|
||||||
const hwHex = encodeHardware(3, 12, 20);
|
|
||||||
console.log(hwHex); // "0D94"
|
|
||||||
console.log(hexToBinary(hwHex)); // "0000 1101 1001 0100"
|
|
||||||
console.log(decodeHardware(hwHex)); // { type: 3, style: 12, color: 20 }
|
|
||||||
|
|
||||||
// Example 6: Validate configuration
|
|
||||||
const validation = validateConfiguration({
|
|
||||||
type: 1, color: 4, material: 1,
|
|
||||||
hardware: [{ type: 1, style: 1, color: 1 }]
|
|
||||||
});
|
|
||||||
console.log(validation); // { valid: true, errors: [] }
|
|
||||||
*/
|
|
||||||
@@ -1,652 +0,0 @@
|
|||||||
"""
|
|
||||||
CSV to JSON Parser - Generate Navigation, Products, and Accessories JSON Files
|
|
||||||
Based on AI_PARSING_INSTRUCTIONS.md
|
|
||||||
|
|
||||||
This script reads data/products.csv and generates:
|
|
||||||
1. data/navigation.json - Question flow with conditional logic
|
|
||||||
2. data/products.json - Product catalog
|
|
||||||
3. data/accessories.json - Accessory options with compatibility rules
|
|
||||||
|
|
||||||
Run: python parse_csv_to_json.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import csv
|
|
||||||
import json
|
|
||||||
from collections import defaultdict
|
|
||||||
from typing import Dict, List, Set, Tuple
|
|
||||||
|
|
||||||
def parse_bool(value):
|
|
||||||
"""Parse TRUE/FALSE string to boolean"""
|
|
||||||
if isinstance(value, str):
|
|
||||||
return value.strip().upper() == 'TRUE'
|
|
||||||
return bool(value)
|
|
||||||
|
|
||||||
def clean_string(value):
|
|
||||||
"""Clean and strip string values"""
|
|
||||||
if isinstance(value, str):
|
|
||||||
return value.strip()
|
|
||||||
return value or ""
|
|
||||||
|
|
||||||
class CSVParser:
|
|
||||||
def __init__(self, csv_path: str):
|
|
||||||
self.csv_path = csv_path
|
|
||||||
self.products = []
|
|
||||||
self.accessories = []
|
|
||||||
self.base_types = set()
|
|
||||||
self.door_subtypes = defaultdict(list) # subtype -> [products]
|
|
||||||
self.window_subtypes = defaultdict(list)
|
|
||||||
self.material_counts = defaultdict(lambda: {'aluminum': 0, 'vinyl': 0})
|
|
||||||
self.color_counts = defaultdict(lambda: defaultdict(int)) # subtype -> {color: count}
|
|
||||||
self.color_by_material = defaultdict(lambda: defaultdict(set)) # subtype -> {material: {colors}}
|
|
||||||
self.detailed_products = {} # Load from questions.json
|
|
||||||
|
|
||||||
def load_detailed_product_info(self):
|
|
||||||
"""Load detailed product info from questions.json"""
|
|
||||||
try:
|
|
||||||
# Load the code mapping
|
|
||||||
try:
|
|
||||||
with open('data/product_code_mapping.json', 'r', encoding='utf-8') as f:
|
|
||||||
code_mapping = json.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
code_mapping = {}
|
|
||||||
|
|
||||||
with open('data/questions.json', 'r', encoding='utf-8') as f:
|
|
||||||
questions_data = json.load(f)
|
|
||||||
|
|
||||||
# Extract all product nodes
|
|
||||||
for key, value in questions_data.items():
|
|
||||||
if key.startswith('prod-') and value.get('type') == 'product':
|
|
||||||
code = value.get('code')
|
|
||||||
if code:
|
|
||||||
detailed_info = {
|
|
||||||
'title': value.get('title'),
|
|
||||||
'detailedDescription': value.get('description'),
|
|
||||||
'features': value.get('features', []),
|
|
||||||
'image': value.get('image'),
|
|
||||||
'url': value.get('url'),
|
|
||||||
'brochure': value.get('brochure'),
|
|
||||||
'measureGuide': value.get('measureGuide')
|
|
||||||
}
|
|
||||||
|
|
||||||
# Store under the simplified code
|
|
||||||
self.detailed_products[code] = detailed_info
|
|
||||||
|
|
||||||
# Also store under all mapped codes
|
|
||||||
for csv_code, simple_code in code_mapping.items():
|
|
||||||
if simple_code == code:
|
|
||||||
self.detailed_products[csv_code] = detailed_info
|
|
||||||
|
|
||||||
print(f"✓ Loaded {len(self.detailed_products)} detailed product descriptions")
|
|
||||||
except FileNotFoundError:
|
|
||||||
print("⚠ questions.json not found - skipping detailed product info")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠ Error loading product details: {e}")
|
|
||||||
|
|
||||||
def parse_csv(self):
|
|
||||||
"""Parse the CSV file and separate products from accessories"""
|
|
||||||
print("📖 Reading CSV file...")
|
|
||||||
|
|
||||||
with open(self.csv_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
||||||
reader = csv.reader(f)
|
|
||||||
|
|
||||||
# Skip first two header rows
|
|
||||||
next(reader) # Row 1: Category headers
|
|
||||||
header_row = next(reader) # Row 2: Column names
|
|
||||||
|
|
||||||
row_count = 0
|
|
||||||
for row in reader:
|
|
||||||
if len(row) < 11: # Need at least up to column K
|
|
||||||
continue
|
|
||||||
|
|
||||||
row_count += 1
|
|
||||||
|
|
||||||
# Skip discontinued items
|
|
||||||
if parse_bool(row[0]): # DISCONT column
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Extract basic info
|
|
||||||
prod_code = clean_string(row[2]) # PROD_CODE
|
|
||||||
category = clean_string(row[3]) # CATEGORY
|
|
||||||
description = clean_string(row[5]) # DESCRIPTION
|
|
||||||
location = clean_string(row[1]) # LOC_CODE
|
|
||||||
|
|
||||||
base_type = clean_string(row[6]) if len(row) > 6 else "" # Base Type (G)
|
|
||||||
door_subtype = clean_string(row[7]) if len(row) > 7 else "" # Sub-type Door (H)
|
|
||||||
window_subtype = clean_string(row[8]) if len(row) > 8 else "" # Sub-type Window (I)
|
|
||||||
is_accessory = parse_bool(row[9]) if len(row) > 9 else False # Accessory Yes (J)
|
|
||||||
specific_item = parse_bool(row[10]) if len(row) > 10 else False # This Item (K)
|
|
||||||
|
|
||||||
# Parse materials (L, M)
|
|
||||||
materials = []
|
|
||||||
if len(row) > 11 and parse_bool(row[11]):
|
|
||||||
materials.append('Aluminum')
|
|
||||||
if len(row) > 12 and parse_bool(row[12]):
|
|
||||||
materials.append('Vinyl')
|
|
||||||
|
|
||||||
# Parse colors (N through S+)
|
|
||||||
colors = []
|
|
||||||
color_columns = [
|
|
||||||
(13, 'Black'), (14, 'White'), (15, 'Bronze'),
|
|
||||||
(16, 'Tan'), (17, 'Mill'), (18, 'Sandstone')
|
|
||||||
]
|
|
||||||
for col_idx, color_name in color_columns:
|
|
||||||
if len(row) > col_idx and parse_bool(row[col_idx]):
|
|
||||||
colors.append(color_name)
|
|
||||||
|
|
||||||
# Build item data
|
|
||||||
item_data = {
|
|
||||||
'id': prod_code,
|
|
||||||
'productCode': prod_code,
|
|
||||||
'category': category,
|
|
||||||
'description': description,
|
|
||||||
'discontinued': False,
|
|
||||||
'location': location,
|
|
||||||
'baseType': base_type,
|
|
||||||
'subType': {
|
|
||||||
'door': door_subtype if door_subtype else None,
|
|
||||||
'window': window_subtype if window_subtype else None
|
|
||||||
},
|
|
||||||
'materials': materials,
|
|
||||||
'colors': colors
|
|
||||||
}
|
|
||||||
|
|
||||||
# Separate accessories from products
|
|
||||||
if is_accessory:
|
|
||||||
# Build accessory-specific data
|
|
||||||
accessory = {
|
|
||||||
**item_data,
|
|
||||||
'accessoryCode': prod_code,
|
|
||||||
'compatibilityRules': self._build_compatibility_rules(
|
|
||||||
door_subtype, window_subtype, category, specific_item
|
|
||||||
),
|
|
||||||
'optionType': self._infer_option_type(description),
|
|
||||||
'metadata': {
|
|
||||||
'specificItemLink': specific_item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.accessories.append(accessory)
|
|
||||||
else:
|
|
||||||
# Build product-specific data
|
|
||||||
product = {
|
|
||||||
**item_data,
|
|
||||||
'isAccessory': False,
|
|
||||||
'compatibleAccessories': [] # Will be populated later
|
|
||||||
}
|
|
||||||
|
|
||||||
# Merge detailed info if available
|
|
||||||
if prod_code in self.detailed_products:
|
|
||||||
detailed = self.detailed_products[prod_code]
|
|
||||||
product.update({
|
|
||||||
'title': detailed['title'],
|
|
||||||
'detailedDescription': detailed['detailedDescription'],
|
|
||||||
'features': detailed['features'],
|
|
||||||
'image': detailed['image'],
|
|
||||||
'url': detailed['url'],
|
|
||||||
'brochure': detailed.get('brochure'),
|
|
||||||
'measureGuide': detailed.get('measureGuide')
|
|
||||||
})
|
|
||||||
|
|
||||||
self.products.append(product)
|
|
||||||
|
|
||||||
# Track for navigation building
|
|
||||||
if base_type:
|
|
||||||
self.base_types.add(base_type)
|
|
||||||
|
|
||||||
# Track subtypes per type
|
|
||||||
if door_subtype:
|
|
||||||
self.door_subtypes[door_subtype].append(product)
|
|
||||||
if window_subtype:
|
|
||||||
self.window_subtypes[window_subtype].append(product)
|
|
||||||
|
|
||||||
# Count materials per subtype
|
|
||||||
subtype_key = door_subtype or window_subtype or "none"
|
|
||||||
if 'Aluminum' in materials:
|
|
||||||
self.material_counts[subtype_key]['aluminum'] += 1
|
|
||||||
# Track colors per material
|
|
||||||
for color in colors:
|
|
||||||
self.color_by_material[subtype_key]['Aluminum'].add(color)
|
|
||||||
if 'Vinyl' in materials:
|
|
||||||
self.material_counts[subtype_key]['vinyl'] += 1
|
|
||||||
# Track colors per material
|
|
||||||
for color in colors:
|
|
||||||
self.color_by_material[subtype_key]['Vinyl'].add(color)
|
|
||||||
|
|
||||||
# Count colors per subtype (overall)
|
|
||||||
for color in colors:
|
|
||||||
self.color_counts[subtype_key][color] += 1
|
|
||||||
|
|
||||||
print(f"✓ Parsed {row_count} rows")
|
|
||||||
print(f"✓ Found {len(self.products)} products")
|
|
||||||
print(f"✓ Found {len(self.accessories)} accessories")
|
|
||||||
|
|
||||||
def _build_compatibility_rules(self, door_subtype, window_subtype, category, specific_item):
|
|
||||||
"""Build compatibility rules for accessories"""
|
|
||||||
rules = {
|
|
||||||
'type': 'category', # Default
|
|
||||||
'subTypeDoor': door_subtype if door_subtype else None,
|
|
||||||
'subTypeWindow': window_subtype if window_subtype else None,
|
|
||||||
'categories': [category] if category else [],
|
|
||||||
'specificProducts': [],
|
|
||||||
'requiresMatch': {
|
|
||||||
'material': True, # Usually accessories need matching material
|
|
||||||
'color': False # Colors usually don't need to match
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Determine rule type
|
|
||||||
if specific_item:
|
|
||||||
rules['type'] = 'specific'
|
|
||||||
elif door_subtype or window_subtype:
|
|
||||||
rules['type'] = 'subType'
|
|
||||||
|
|
||||||
return rules
|
|
||||||
|
|
||||||
def _infer_option_type(self, description):
|
|
||||||
"""Infer option type from description"""
|
|
||||||
desc_upper = description.upper()
|
|
||||||
|
|
||||||
if 'HANDLE' in desc_upper:
|
|
||||||
return 'handle'
|
|
||||||
elif 'INSERT' in desc_upper or 'GLASS' in desc_upper:
|
|
||||||
return 'insert'
|
|
||||||
elif 'HARDWARE' in desc_upper:
|
|
||||||
return 'hardware'
|
|
||||||
elif 'SCREEN' in desc_upper:
|
|
||||||
return 'screen'
|
|
||||||
else:
|
|
||||||
return 'option'
|
|
||||||
|
|
||||||
def link_accessories_to_products(self):
|
|
||||||
"""Link compatible accessories to products"""
|
|
||||||
print("🔗 Linking accessories to products...")
|
|
||||||
|
|
||||||
linked_count = 0
|
|
||||||
for product in self.products:
|
|
||||||
compatible = []
|
|
||||||
|
|
||||||
for accessory in self.accessories:
|
|
||||||
rules = accessory['compatibilityRules']
|
|
||||||
|
|
||||||
# Check by subtype
|
|
||||||
if rules['type'] == 'subType':
|
|
||||||
prod_door = product['subType']['door']
|
|
||||||
prod_window = product['subType']['window']
|
|
||||||
acc_door = rules['subTypeDoor']
|
|
||||||
acc_window = rules['subTypeWindow']
|
|
||||||
|
|
||||||
if (prod_door and prod_door == acc_door) or \
|
|
||||||
(prod_window and prod_window == acc_window):
|
|
||||||
# Check material compatibility if required
|
|
||||||
if rules['requiresMatch']['material']:
|
|
||||||
# Check if they share any materials
|
|
||||||
if any(m in product['materials'] for m in accessory['materials']):
|
|
||||||
compatible.append(accessory['id'])
|
|
||||||
linked_count += 1
|
|
||||||
else:
|
|
||||||
compatible.append(accessory['id'])
|
|
||||||
linked_count += 1
|
|
||||||
|
|
||||||
# Check by category
|
|
||||||
elif rules['type'] == 'category':
|
|
||||||
if product['category'] in rules['categories']:
|
|
||||||
compatible.append(accessory['id'])
|
|
||||||
linked_count += 1
|
|
||||||
|
|
||||||
product['compatibleAccessories'] = compatible
|
|
||||||
|
|
||||||
print(f"✓ Created {linked_count} product-accessory links")
|
|
||||||
|
|
||||||
def build_navigation(self):
|
|
||||||
"""Build navigation JSON with conditional logic"""
|
|
||||||
print("🗺️ Building navigation flow...")
|
|
||||||
|
|
||||||
navigation = {}
|
|
||||||
|
|
||||||
# Question 1: Base Type Selection
|
|
||||||
navigation['start'] = {
|
|
||||||
'type': 'question',
|
|
||||||
'inputType': 'button',
|
|
||||||
'title': 'What are you looking for?',
|
|
||||||
'subtitle': 'Select the product category',
|
|
||||||
'answers': []
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add base type options
|
|
||||||
sorted_types = sorted(self.base_types)
|
|
||||||
for base_type in sorted_types:
|
|
||||||
emoji = '🚪' if 'door' in base_type.lower() else '🪟'
|
|
||||||
next_key = f'q-{base_type.lower()}-type'
|
|
||||||
|
|
||||||
navigation['start']['answers'].append({
|
|
||||||
'caption': base_type,
|
|
||||||
'image': emoji,
|
|
||||||
'next': next_key,
|
|
||||||
'filter': {
|
|
||||||
'baseType': base_type
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# Question 2: Sub-type Selection for Doors
|
|
||||||
if self.door_subtypes:
|
|
||||||
door_subtypes = sorted(self.door_subtypes.keys())
|
|
||||||
navigation['q-door-type'] = {
|
|
||||||
'type': 'question',
|
|
||||||
'inputType': 'button',
|
|
||||||
'title': 'What type of door?',
|
|
||||||
'subtitle': 'Select the door category',
|
|
||||||
'answers': []
|
|
||||||
}
|
|
||||||
|
|
||||||
for subtype in door_subtypes:
|
|
||||||
products = self.door_subtypes[subtype]
|
|
||||||
|
|
||||||
# Check if material question is needed
|
|
||||||
needs_material = self._needs_material_question(subtype)
|
|
||||||
|
|
||||||
# Determine next question
|
|
||||||
if needs_material:
|
|
||||||
next_key = f'q-material-{subtype.lower().replace(" ", "-")}'
|
|
||||||
else:
|
|
||||||
# No material choice needed - determine single material and check colors
|
|
||||||
single_material = 'Aluminum' if self.material_counts[subtype]['aluminum'] > 0 else 'Vinyl'
|
|
||||||
needs_color = self._needs_color_question(subtype, single_material)
|
|
||||||
next_key = f'q-color-{subtype.lower().replace(" ", "-")}-{single_material.lower()}' if needs_color else 'q-dimensions'
|
|
||||||
|
|
||||||
# Auto-apply material if only one
|
|
||||||
filter_obj = {
|
|
||||||
'baseType': 'Door',
|
|
||||||
'subType': subtype
|
|
||||||
}
|
|
||||||
|
|
||||||
if not needs_material:
|
|
||||||
# Auto-apply the single material
|
|
||||||
if self.material_counts[subtype]['aluminum'] > 0:
|
|
||||||
filter_obj['material'] = 'Aluminum'
|
|
||||||
elif self.material_counts[subtype]['vinyl'] > 0:
|
|
||||||
filter_obj['material'] = 'Vinyl'
|
|
||||||
|
|
||||||
# If no color question either, auto-apply single color
|
|
||||||
single_material = filter_obj['material']
|
|
||||||
if not self._needs_color_question(subtype, single_material):
|
|
||||||
colors = list(self.color_by_material[subtype][single_material])
|
|
||||||
if len(colors) == 1:
|
|
||||||
filter_obj['color'] = colors[0]
|
|
||||||
|
|
||||||
navigation['q-door-type']['answers'].append({
|
|
||||||
'caption': subtype,
|
|
||||||
'image': '🚪',
|
|
||||||
'next': next_key,
|
|
||||||
'filter': filter_obj
|
|
||||||
})
|
|
||||||
|
|
||||||
# Create material question if needed
|
|
||||||
if needs_material:
|
|
||||||
self._create_material_question(navigation, subtype, 'Door')
|
|
||||||
# Create color question if material skipped but color needed
|
|
||||||
elif next_key.startswith('q-color-'):
|
|
||||||
single_material = 'Aluminum' if self.material_counts[subtype]['aluminum'] > 0 else 'Vinyl'
|
|
||||||
self._create_color_question(navigation, subtype, single_material, 'Door')
|
|
||||||
|
|
||||||
# Question 2: Sub-type Selection for Windows
|
|
||||||
if self.window_subtypes:
|
|
||||||
window_subtypes = sorted(self.window_subtypes.keys())
|
|
||||||
navigation['q-window-type'] = {
|
|
||||||
'type': 'question',
|
|
||||||
'inputType': 'button',
|
|
||||||
'title': 'What type of window?',
|
|
||||||
'subtitle': 'Select the window category',
|
|
||||||
'answers': []
|
|
||||||
}
|
|
||||||
|
|
||||||
for subtype in window_subtypes:
|
|
||||||
needs_material = self._needs_material_question(subtype)
|
|
||||||
|
|
||||||
# Determine next question
|
|
||||||
if needs_material:
|
|
||||||
next_key = f'q-material-{subtype.lower().replace(" ", "-")}'
|
|
||||||
else:
|
|
||||||
# No material choice needed - determine single material and check colors
|
|
||||||
single_material = 'Aluminum' if self.material_counts[subtype]['aluminum'] > 0 else 'Vinyl'
|
|
||||||
needs_color = self._needs_color_question(subtype, single_material)
|
|
||||||
next_key = f'q-color-{subtype.lower().replace(" ", "-")}-{single_material.lower()}' if needs_color else 'q-dimensions'
|
|
||||||
|
|
||||||
filter_obj = {
|
|
||||||
'baseType': 'Window',
|
|
||||||
'subType': subtype
|
|
||||||
}
|
|
||||||
|
|
||||||
if not needs_material:
|
|
||||||
if self.material_counts[subtype]['aluminum'] > 0:
|
|
||||||
filter_obj['material'] = 'Aluminum'
|
|
||||||
elif self.material_counts[subtype]['vinyl'] > 0:
|
|
||||||
filter_obj['material'] = 'Vinyl'
|
|
||||||
|
|
||||||
# If no color question either, auto-apply single color
|
|
||||||
single_material = filter_obj['material']
|
|
||||||
if not self._needs_color_question(subtype, single_material):
|
|
||||||
colors = list(self.color_by_material[subtype][single_material])
|
|
||||||
if len(colors) == 1:
|
|
||||||
filter_obj['color'] = colors[0]
|
|
||||||
|
|
||||||
navigation['q-window-type']['answers'].append({
|
|
||||||
'caption': subtype,
|
|
||||||
'image': '🪟',
|
|
||||||
'next': next_key,
|
|
||||||
'filter': filter_obj
|
|
||||||
})
|
|
||||||
|
|
||||||
if needs_material:
|
|
||||||
self._create_material_question(navigation, subtype, 'Window')
|
|
||||||
# Create color question if material skipped but color needed
|
|
||||||
elif next_key.startswith('q-color-'):
|
|
||||||
single_material = 'Aluminum' if self.material_counts[subtype]['aluminum'] > 0 else 'Vinyl'
|
|
||||||
self._create_color_question(navigation, subtype, single_material, 'Window')
|
|
||||||
|
|
||||||
# Final Question: Dimensions
|
|
||||||
navigation['q-dimensions'] = {
|
|
||||||
'type': 'question',
|
|
||||||
'inputType': 'form',
|
|
||||||
'title': 'Enter Product Dimensions',
|
|
||||||
'subtitle': 'Please provide the measurements',
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'name': 'width',
|
|
||||||
'label': 'Width (inches)',
|
|
||||||
'type': 'number',
|
|
||||||
'required': True,
|
|
||||||
'placeholder': 'e.g., 36'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'height',
|
|
||||||
'label': 'Height (inches)',
|
|
||||||
'type': 'number',
|
|
||||||
'required': True,
|
|
||||||
'placeholder': 'e.g., 80'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'next': 'results'
|
|
||||||
}
|
|
||||||
|
|
||||||
print(f"✓ Created {len(navigation)} navigation nodes")
|
|
||||||
return navigation
|
|
||||||
|
|
||||||
def _needs_material_question(self, subtype):
|
|
||||||
"""Check if a subtype needs a material selection question"""
|
|
||||||
aluminum = self.material_counts[subtype]['aluminum']
|
|
||||||
vinyl = self.material_counts[subtype]['vinyl']
|
|
||||||
|
|
||||||
# Show material question if both materials exist
|
|
||||||
return aluminum > 0 and vinyl > 0
|
|
||||||
|
|
||||||
def _create_material_question(self, navigation, subtype, base_type):
|
|
||||||
"""Create a material selection question"""
|
|
||||||
question_key = f'q-material-{subtype.lower().replace(" ", "-")}'
|
|
||||||
|
|
||||||
navigation[question_key] = {
|
|
||||||
'type': 'question',
|
|
||||||
'inputType': 'button',
|
|
||||||
'title': 'Select Material',
|
|
||||||
'subtitle': f'Choose your preferred material for {subtype}',
|
|
||||||
'conditional': {
|
|
||||||
'type': 'material',
|
|
||||||
'fallbackNext': 'q-dimensions'
|
|
||||||
},
|
|
||||||
'answers': []
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add material options
|
|
||||||
if self.material_counts[subtype]['aluminum'] > 0:
|
|
||||||
# Check if color question needed after aluminum selection
|
|
||||||
needs_color = self._needs_color_question(subtype, 'Aluminum')
|
|
||||||
next_key_al = f'q-color-{subtype.lower().replace(" ", "-")}-aluminum' if needs_color else 'q-dimensions'
|
|
||||||
|
|
||||||
navigation[question_key]['answers'].append({
|
|
||||||
'caption': 'Aluminum',
|
|
||||||
'image': '🔩',
|
|
||||||
'next': next_key_al,
|
|
||||||
'filter': {
|
|
||||||
'baseType': base_type,
|
|
||||||
'subType': subtype,
|
|
||||||
'material': 'Aluminum'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if self.material_counts[subtype]['vinyl'] > 0:
|
|
||||||
# Check if color question needed after vinyl selection
|
|
||||||
needs_color = self._needs_color_question(subtype, 'Vinyl')
|
|
||||||
next_key_vin = f'q-color-{subtype.lower().replace(" ", "-")}-vinyl' if needs_color else 'q-dimensions'
|
|
||||||
|
|
||||||
navigation[question_key]['answers'].append({
|
|
||||||
'caption': 'Vinyl',
|
|
||||||
'image': '🪟',
|
|
||||||
'next': next_key_vin,
|
|
||||||
'filter': {
|
|
||||||
'baseType': base_type,
|
|
||||||
'subType': subtype,
|
|
||||||
'material': 'Vinyl'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# Create color questions for each material option
|
|
||||||
if self.material_counts[subtype]['aluminum'] > 0 and self._needs_color_question(subtype, 'Aluminum'):
|
|
||||||
self._create_color_question(navigation, subtype, 'Aluminum', base_type)
|
|
||||||
if self.material_counts[subtype]['vinyl'] > 0 and self._needs_color_question(subtype, 'Vinyl'):
|
|
||||||
self._create_color_question(navigation, subtype, 'Vinyl', base_type)
|
|
||||||
|
|
||||||
def _needs_color_question(self, subtype, material):
|
|
||||||
"""Check if a subtype+material combination needs a color selection question"""
|
|
||||||
colors = self.color_by_material[subtype][material]
|
|
||||||
# Show color question if multiple colors exist
|
|
||||||
return len(colors) > 1
|
|
||||||
|
|
||||||
def _create_color_question(self, navigation, subtype, material, base_type):
|
|
||||||
"""Create a color selection question"""
|
|
||||||
question_key = f'q-color-{subtype.lower().replace(" ", "-")}-{material.lower()}'
|
|
||||||
|
|
||||||
navigation[question_key] = {
|
|
||||||
'type': 'question',
|
|
||||||
'inputType': 'button',
|
|
||||||
'title': 'Select Color',
|
|
||||||
'subtitle': f'Choose your preferred color for {material} {subtype}',
|
|
||||||
'conditional': {
|
|
||||||
'type': 'color',
|
|
||||||
'fallbackNext': 'q-dimensions'
|
|
||||||
},
|
|
||||||
'answers': []
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get available colors for this subtype+material
|
|
||||||
colors = sorted(self.color_by_material[subtype][material])
|
|
||||||
|
|
||||||
# Color emoji mapping
|
|
||||||
color_emojis = {
|
|
||||||
'Black': '⬛',
|
|
||||||
'White': '⬜',
|
|
||||||
'Bronze': '🟫',
|
|
||||||
'Tan': '🟤',
|
|
||||||
'Mill': '⚪',
|
|
||||||
'Sandstone': '🟨'
|
|
||||||
}
|
|
||||||
|
|
||||||
for color in colors:
|
|
||||||
navigation[question_key]['answers'].append({
|
|
||||||
'caption': color,
|
|
||||||
'image': color_emojis.get(color, '🎨'),
|
|
||||||
'next': 'q-dimensions',
|
|
||||||
'filter': {
|
|
||||||
'baseType': base_type,
|
|
||||||
'subType': subtype,
|
|
||||||
'material': material,
|
|
||||||
'color': color
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
def save_json_files(self, navigation):
|
|
||||||
"""Save all three JSON files"""
|
|
||||||
print("💾 Saving JSON files...")
|
|
||||||
|
|
||||||
# Save products.json
|
|
||||||
with open('data/products.json', 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(self.products, f, indent=2)
|
|
||||||
print(f"✓ Saved data/products.json ({len(self.products)} products)")
|
|
||||||
|
|
||||||
# Save accessories.json
|
|
||||||
with open('data/accessories.json', 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(self.accessories, f, indent=2)
|
|
||||||
print(f"✓ Saved data/accessories.json ({len(self.accessories)} accessories)")
|
|
||||||
|
|
||||||
# Save navigation.json
|
|
||||||
with open('data/navigation.json', 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(navigation, f, indent=2)
|
|
||||||
print(f"✓ Saved data/navigation.json ({len(navigation)} nodes)")
|
|
||||||
|
|
||||||
def print_statistics(self):
|
|
||||||
"""Print parsing statistics"""
|
|
||||||
print("\n" + "="*60)
|
|
||||||
print("PARSING STATISTICS")
|
|
||||||
print("="*60)
|
|
||||||
print(f"Total Products: {len(self.products)}")
|
|
||||||
print(f"Total Accessories: {len(self.accessories)}")
|
|
||||||
print(f"Base Types: {', '.join(sorted(self.base_types))}")
|
|
||||||
print(f"Door Subtypes: {len(self.door_subtypes)}")
|
|
||||||
print(f"Window Subtypes: {len(self.window_subtypes)}")
|
|
||||||
print()
|
|
||||||
print("Material Distribution:")
|
|
||||||
for subtype, counts in sorted(self.material_counts.items()):
|
|
||||||
if counts['aluminum'] > 0 or counts['vinyl'] > 0:
|
|
||||||
needs_q = "✓ Material Q" if (counts['aluminum'] > 0 and counts['vinyl'] > 0) else "✗ Skip"
|
|
||||||
print(f" {subtype:20} - Al:{counts['aluminum']:3} Vinyl:{counts['vinyl']:3} [{needs_q}]")
|
|
||||||
print("="*60)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
csv_path = 'data/products.csv'
|
|
||||||
|
|
||||||
print("🚀 CSV to JSON Parser")
|
|
||||||
print("="*60)
|
|
||||||
|
|
||||||
try:
|
|
||||||
parser = CSVParser(csv_path)
|
|
||||||
parser.load_detailed_product_info()
|
|
||||||
parser.parse_csv()
|
|
||||||
parser.link_accessories_to_products()
|
|
||||||
navigation = parser.build_navigation()
|
|
||||||
parser.save_json_files(navigation)
|
|
||||||
parser.print_statistics()
|
|
||||||
|
|
||||||
print("\n✅ All JSON files generated successfully!")
|
|
||||||
print("\nNext steps:")
|
|
||||||
print("1. Review data/navigation.json for question flow")
|
|
||||||
print("2. Review data/products.json for product data")
|
|
||||||
print("3. Review data/accessories.json for accessory compatibility")
|
|
||||||
print("4. Test the application in your browser")
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
print(f"❌ Error: Could not find {csv_path}")
|
|
||||||
print(" Make sure products.csv exists in the data/ folder")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
+12
-42
@@ -1,47 +1,17 @@
|
|||||||
"""
|
import importlib.machinery
|
||||||
Passenger WSGI file for Flask application
|
import importlib.util
|
||||||
Compatible with Python 3.13+
|
|
||||||
Auto-detects /product-finder subdirectory deployment
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
# Get the directory where this file is located
|
|
||||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
# Add the application directory to Python path
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
sys.path.insert(0, CURRENT_DIR)
|
|
||||||
|
|
||||||
# Import the Flask application directly from app.py
|
def load_source(modname, filename):
|
||||||
# Note: app.py now auto-detects production environment
|
loader = importlib.machinery.SourceFileLoader(modname, filename)
|
||||||
try:
|
spec = importlib.util.spec_from_file_location(modname, filename, loader=loader)
|
||||||
from app import app as application
|
module = importlib.util.module_from_spec(spec)
|
||||||
print("✓ Flask application loaded successfully")
|
loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
except ImportError as e:
|
wsgi = load_source('wsgi', 'app.py')
|
||||||
# Fallback error handler that shows the actual error
|
application = wsgi.application
|
||||||
def application(environ, start_response):
|
|
||||||
import traceback
|
|
||||||
status = '500 Internal Server Error'
|
|
||||||
error_details = f'Import Error: {str(e)}\n\nTraceback:\n{traceback.format_exc()}\n\nPython Path:\n{sys.path}'
|
|
||||||
output = error_details.encode('utf-8')
|
|
||||||
response_headers = [('Content-type', 'text/plain; charset=utf-8'),
|
|
||||||
('Content-Length', str(len(output)))]
|
|
||||||
start_response(status, response_headers)
|
|
||||||
return [output]
|
|
||||||
print(f"✗ Failed to import Flask app: {e}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# Catch any other startup errors
|
|
||||||
def application(environ, start_response):
|
|
||||||
import traceback
|
|
||||||
status = '500 Internal Server Error'
|
|
||||||
error_details = f'Startup Error: {str(e)}\n\nTraceback:\n{traceback.format_exc()}'
|
|
||||||
output = error_details.encode('utf-8')
|
|
||||||
response_headers = [('Content-type', 'text/plain; charset=utf-8'),
|
|
||||||
('Content-Length', str(len(output)))]
|
|
||||||
start_response(status, response_headers)
|
|
||||||
return [output]
|
|
||||||
print(f"✗ Error starting app: {e}")
|
|
||||||
|
|
||||||
# Passenger requires the application object to be named 'application'
|
|
||||||
|
|||||||
@@ -1,189 +0,0 @@
|
|||||||
"""
|
|
||||||
Interactive Region Coordinate Helper
|
|
||||||
Opens an image and helps you find pixel coordinates for colorable regions.
|
|
||||||
Click on the image to see coordinates.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python region_helper.py images/cobrai.jpg
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
|
||||||
import tkinter as tk
|
|
||||||
from tkinter import Canvas
|
|
||||||
from PIL import ImageTk
|
|
||||||
|
|
||||||
|
|
||||||
class RegionHelper:
|
|
||||||
def __init__(self, image_path):
|
|
||||||
self.image_path = image_path
|
|
||||||
self.image = Image.open(image_path)
|
|
||||||
self.width, self.height = self.image.size
|
|
||||||
|
|
||||||
# Setup UI
|
|
||||||
self.root = tk.Tk()
|
|
||||||
self.root.title(f"Region Helper - {image_path}")
|
|
||||||
|
|
||||||
# Scale image if too large
|
|
||||||
self.scale = 1.0
|
|
||||||
max_display_width = 1000
|
|
||||||
max_display_height = 800
|
|
||||||
|
|
||||||
if self.width > max_display_width or self.height > max_display_height:
|
|
||||||
scale_w = max_display_width / self.width
|
|
||||||
scale_h = max_display_height / self.height
|
|
||||||
self.scale = min(scale_w, scale_h)
|
|
||||||
|
|
||||||
new_width = int(self.width * self.scale)
|
|
||||||
new_height = int(self.height * self.scale)
|
|
||||||
self.display_image = self.image.resize((new_width, new_height), Image.LANCZOS)
|
|
||||||
else:
|
|
||||||
self.display_image = self.image.copy()
|
|
||||||
|
|
||||||
# Setup canvas
|
|
||||||
self.canvas = Canvas(
|
|
||||||
self.root,
|
|
||||||
width=self.display_image.width,
|
|
||||||
height=self.display_image.height
|
|
||||||
)
|
|
||||||
self.canvas.pack(side=tk.LEFT)
|
|
||||||
|
|
||||||
# Display image
|
|
||||||
self.photo = ImageTk.PhotoImage(self.display_image)
|
|
||||||
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.photo)
|
|
||||||
|
|
||||||
# Info panel
|
|
||||||
info_frame = tk.Frame(self.root, width=300)
|
|
||||||
info_frame.pack(side=tk.RIGHT, fill=tk.BOTH, padx=10, pady=10)
|
|
||||||
|
|
||||||
tk.Label(info_frame, text="Image Dimensions:", font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=5)
|
|
||||||
tk.Label(info_frame, text=f"{self.width} x {self.height} pixels").pack(anchor=tk.W)
|
|
||||||
|
|
||||||
if self.scale != 1.0:
|
|
||||||
tk.Label(info_frame, text=f"Scaled: {self.scale:.2%}").pack(anchor=tk.W)
|
|
||||||
|
|
||||||
tk.Label(info_frame, text="\nClick to get coordinates:", font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=(20, 5))
|
|
||||||
|
|
||||||
self.coord_label = tk.Label(info_frame, text="X: -, Y: -", font=('Arial', 14))
|
|
||||||
self.coord_label.pack(anchor=tk.W, pady=5)
|
|
||||||
|
|
||||||
tk.Label(info_frame, text="\nFirst click = Top-Left", fg='blue').pack(anchor=tk.W)
|
|
||||||
tk.Label(info_frame, text="Second click = Bottom-Right", fg='blue').pack(anchor=tk.W)
|
|
||||||
|
|
||||||
tk.Label(info_frame, text="\nRegion JSON:", font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=(20, 5))
|
|
||||||
|
|
||||||
self.json_text = tk.Text(info_frame, height=15, width=35, font=('Courier', 9))
|
|
||||||
self.json_text.pack(fill=tk.BOTH, expand=True)
|
|
||||||
|
|
||||||
tk.Button(info_frame, text="Clear Points", command=self.clear_points).pack(pady=10)
|
|
||||||
tk.Button(info_frame, text="Copy JSON", command=self.copy_json).pack()
|
|
||||||
|
|
||||||
# Bind events
|
|
||||||
self.canvas.bind('<Motion>', self.on_mouse_move)
|
|
||||||
self.canvas.bind('<Button-1>', self.on_click)
|
|
||||||
|
|
||||||
# Store points
|
|
||||||
self.points = []
|
|
||||||
self.point_ids = []
|
|
||||||
|
|
||||||
self.update_json()
|
|
||||||
|
|
||||||
def on_mouse_move(self, event):
|
|
||||||
"""Show current mouse coordinates."""
|
|
||||||
x = int(event.x / self.scale)
|
|
||||||
y = int(event.y / self.scale)
|
|
||||||
self.coord_label.config(text=f"X: {x}, Y: {y}")
|
|
||||||
|
|
||||||
def on_click(self, event):
|
|
||||||
"""Record clicked point."""
|
|
||||||
x = int(event.x / self.scale)
|
|
||||||
y = int(event.y / self.scale)
|
|
||||||
|
|
||||||
# Limit to 2 points (top-left, bottom-right)
|
|
||||||
if len(self.points) >= 2:
|
|
||||||
self.clear_points()
|
|
||||||
|
|
||||||
self.points.append((x, y))
|
|
||||||
|
|
||||||
# Draw point on canvas
|
|
||||||
color = 'blue' if len(self.points) == 1 else 'red'
|
|
||||||
point_id = self.canvas.create_oval(
|
|
||||||
event.x - 5, event.y - 5,
|
|
||||||
event.x + 5, event.y + 5,
|
|
||||||
fill=color, outline='white', width=2
|
|
||||||
)
|
|
||||||
self.point_ids.append(point_id)
|
|
||||||
|
|
||||||
# Draw rectangle if we have both points
|
|
||||||
if len(self.points) == 2:
|
|
||||||
x1, y1 = self.points[0]
|
|
||||||
x2, y2 = self.points[1]
|
|
||||||
|
|
||||||
# Draw on canvas (scaled)
|
|
||||||
rect_id = self.canvas.create_rectangle(
|
|
||||||
x1 * self.scale, y1 * self.scale,
|
|
||||||
x2 * self.scale, y2 * self.scale,
|
|
||||||
outline='green', width=2, dash=(5, 5)
|
|
||||||
)
|
|
||||||
self.point_ids.append(rect_id)
|
|
||||||
|
|
||||||
self.update_json()
|
|
||||||
|
|
||||||
def clear_points(self):
|
|
||||||
"""Clear all points and rectangles."""
|
|
||||||
for point_id in self.point_ids:
|
|
||||||
self.canvas.delete(point_id)
|
|
||||||
|
|
||||||
self.points = []
|
|
||||||
self.point_ids = []
|
|
||||||
self.update_json()
|
|
||||||
|
|
||||||
def update_json(self):
|
|
||||||
"""Update the JSON display."""
|
|
||||||
self.json_text.delete('1.0', tk.END)
|
|
||||||
|
|
||||||
if len(self.points) == 0:
|
|
||||||
self.json_text.insert('1.0', '{\n "name": "region_name",\n "topLeft": [?, ?],\n "bottomRight": [?, ?],\n "description": "..."\n}')
|
|
||||||
elif len(self.points) == 1:
|
|
||||||
x, y = self.points[0]
|
|
||||||
self.json_text.insert('1.0', f'{{\n "name": "region_name",\n "topLeft": [{x}, {y}],\n "bottomRight": [?, ?],\n "description": "..."\n}}')
|
|
||||||
else:
|
|
||||||
x1, y1 = self.points[0]
|
|
||||||
x2, y2 = self.points[1]
|
|
||||||
|
|
||||||
# Calculate dimensions
|
|
||||||
width = abs(x2 - x1)
|
|
||||||
height = abs(y2 - y1)
|
|
||||||
|
|
||||||
json_str = f'''{{\n "name": "region_name",\n "topLeft": [{x1}, {y1}],\n "bottomRight": [{x2}, {y2}],\n "description": "Width: {width}px, Height: {height}px"\n}}'''
|
|
||||||
self.json_text.insert('1.0', json_str)
|
|
||||||
|
|
||||||
def copy_json(self):
|
|
||||||
"""Copy JSON to clipboard."""
|
|
||||||
json_str = self.json_text.get('1.0', tk.END).strip()
|
|
||||||
self.root.clipboard_clear()
|
|
||||||
self.root.clipboard_append(json_str)
|
|
||||||
self.root.update()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""Start the GUI."""
|
|
||||||
self.root.mainloop()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Usage: python region_helper.py <image_path>")
|
|
||||||
print("Example: python region_helper.py images/cobrai.jpg")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
image_path = sys.argv[1]
|
|
||||||
|
|
||||||
try:
|
|
||||||
helper = RegionHelper(image_path)
|
|
||||||
helper.run()
|
|
||||||
except FileNotFoundError:
|
|
||||||
print(f"Error: Image file not found: {image_path}")
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
@@ -1,4 +1 @@
|
|||||||
Flask>=3.0.0
|
Flask==3.1.3
|
||||||
Werkzeug>=3.0.0
|
|
||||||
# Pillow>=10.0.0 # Optional - only needed for dynamic image generation
|
|
||||||
# If you need image generation, install separately: pip install Pillow
|
|
||||||
|
|||||||
@@ -28,29 +28,26 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user-info-bar {
|
.user-info-bar {
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 20px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 15px;
|
padding: 2px 10px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn {
|
.logout-btn {
|
||||||
background-color: #f56565;
|
background: none;
|
||||||
color: white;
|
|
||||||
border: none;
|
border: none;
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 13px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
padding: 5px 10px;
|
||||||
transition: background-color 0.3s;
|
transition: transform 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn:hover {
|
.logout-btn:hover {
|
||||||
background-color: #e53e3e;
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.question-container {
|
.question-container {
|
||||||
@@ -41,12 +41,15 @@ function init() {
|
|||||||
|
|
||||||
// Load question data and other JSON files
|
// Load question data and other JSON files
|
||||||
function loadQuestionData() {
|
function loadQuestionData() {
|
||||||
|
// Use the data path from the blueprint
|
||||||
|
const dataPath = '/quiz/data/';
|
||||||
|
|
||||||
// Load all data files in parallel
|
// Load all data files in parallel
|
||||||
Promise.all([
|
Promise.all([
|
||||||
fetch('data/navigation.json').then(r => r.ok ? r.json() : Promise.reject('navigation.json not found')),
|
fetch(dataPath + 'navigation.json').then(r => r.ok ? r.json() : Promise.reject('navigation.json not found')),
|
||||||
fetch('data/products.json').then(r => r.ok ? r.json() : []).catch(() => []),
|
fetch(dataPath + 'products.json').then(r => r.ok ? r.json() : []).catch(() => []),
|
||||||
fetch('data/accessories.json').then(r => r.ok ? r.json() : []).catch(() => []),
|
fetch(dataPath + 'accessories.json').then(r => r.ok ? r.json() : []).catch(() => []),
|
||||||
fetch('data/product_bitwise.json').then(r => r.ok ? r.json() : []).catch(() => [])
|
fetch(dataPath + 'product_bitwise.json').then(r => r.ok ? r.json() : []).catch(() => [])
|
||||||
])
|
])
|
||||||
.then(([questions, products, accessories, bitwise]) => {
|
.then(([questions, products, accessories, bitwise]) => {
|
||||||
questionData = questions;
|
questionData = questions;
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>404 - Page Not Found</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container {
|
|
||||||
text-align: center;
|
|
||||||
background-color: white;
|
|
||||||
padding: 60px 40px;
|
|
||||||
border: 2px solid #333;
|
|
||||||
border-radius: 8px;
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 120px;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 32px;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 12px 30px;
|
|
||||||
background-color: #333;
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 16px;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover {
|
|
||||||
background-color: #555;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="error-container">
|
|
||||||
<h1>404</h1>
|
|
||||||
<h2>Page Not Found</h2>
|
|
||||||
<p>Sorry, the page you're looking for doesn't exist or has been moved.</p>
|
|
||||||
<a href="/" class="button">Go Home</a>
|
|
||||||
<a href="/quiz" class="button" style="margin-left: 10px;">Start Quiz</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Access Denied - CGW Product Finder</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.access-denied-container {
|
|
||||||
background: white;
|
|
||||||
padding: 50px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
||||||
text-align: center;
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
font-size: 80px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #f56565;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.permission-info {
|
|
||||||
background: #fed7d7;
|
|
||||||
color: #742a2a;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin: 20px 0;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
border-left: 4px solid #f56565;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 12px 30px;
|
|
||||||
margin: 10px 5px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background: #5568d3;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: #e0e0e0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary:hover {
|
|
||||||
background: #d0d0d0;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-info {
|
|
||||||
margin-top: 30px;
|
|
||||||
padding-top: 20px;
|
|
||||||
border-top: 2px solid #e0e0e0;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="access-denied-container">
|
|
||||||
<div class="icon">🚫</div>
|
|
||||||
<h1>Access Denied</h1>
|
|
||||||
<p class="message">
|
|
||||||
You do not have permission to access this page or perform this action.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if required_permission %}
|
|
||||||
<div class="permission-info">
|
|
||||||
Required Permission: <strong>{{ required_permission }}</strong>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p class="message">
|
|
||||||
If you believe this is an error, please contact your administrator.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div style="margin-top: 30px;">
|
|
||||||
<a href="javascript:history.back()" class="btn btn-secondary">← Go Back</a>
|
|
||||||
<a href="{{ url_for('index') }}" class="btn btn-primary">Return to Home</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="contact-info">
|
|
||||||
Need help? Contact your system administrator to request access.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,381 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Dynamic Image Generation - Test Page</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-section {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 30px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-container {
|
|
||||||
border: 2px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fafafa;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-container h3 {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-image {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: white;
|
|
||||||
max-height: 500px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
color: #999;
|
|
||||||
font-style: italic;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
background: #f9f9f9;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-group label {
|
|
||||||
display: block;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-group select,
|
|
||||||
.control-group input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-group label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background: #333;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box {
|
|
||||||
background: #e3f2fd;
|
|
||||||
border-left: 4px solid #2196F3;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box h4 {
|
|
||||||
color: #1976D2;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box p {
|
|
||||||
color: #555;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.url-display {
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
word-break: break-all;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
background: #ffebee;
|
|
||||||
border-left: 4px solid #f44336;
|
|
||||||
padding: 15px;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #c62828;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.demo-section {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>🎨 Dynamic Image Generation API</h1>
|
|
||||||
<p class="subtitle">Test the product image generation endpoints</p>
|
|
||||||
|
|
||||||
<div class="info-box">
|
|
||||||
<h4>How It Works</h4>
|
|
||||||
<p>
|
|
||||||
This demo uses server-side image processing to dynamically generate product images
|
|
||||||
with different colors and configurations. Select options below to see the image update in real-time.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="controls">
|
|
||||||
<h3>Product Configuration</h3>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label for="product-select">Product:</label>
|
|
||||||
<select id="product-select">
|
|
||||||
<option value="cobrai">COBRAI - Storm Door</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label for="color-select">Color:</label>
|
|
||||||
<select id="color-select">
|
|
||||||
<option value="white">White</option>
|
|
||||||
<option value="black">Black</option>
|
|
||||||
<option value="bronze">Bronze</option>
|
|
||||||
<option value="sandstone">Sandstone</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label>Hinge Location:</label>
|
|
||||||
<div class="radio-group">
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="hinge" value="right" checked>
|
|
||||||
Right Hinge
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="hinge" value="left">
|
|
||||||
Left Hinge
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="cache-checkbox" checked>
|
|
||||||
Use Cache
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button onclick="updateImages()">🔄 Update Images</button>
|
|
||||||
<button onclick="clearCache()">🗑️ Clear Cache</button>
|
|
||||||
<button onclick="getConfig()">📋 Get Config</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<div class="image-container">
|
|
||||||
<h3>Direct Image URL Method</h3>
|
|
||||||
<p style="color: #666; font-size: 14px; margin-bottom: 10px;">
|
|
||||||
Using: <code><img src="..."></code>
|
|
||||||
</p>
|
|
||||||
<img id="direct-image" class="product-image" src="" alt="Product">
|
|
||||||
<div class="url-display" id="direct-url">Loading...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="image-container">
|
|
||||||
<h3>JSON/Base64 Method</h3>
|
|
||||||
<p style="color: #666; font-size: 14px; margin-bottom: 10px;">
|
|
||||||
Using: <code>fetch() + base64</code>
|
|
||||||
</p>
|
|
||||||
<img id="json-image" class="product-image" src="" alt="Product">
|
|
||||||
<div class="url-display" id="json-url">Loading...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="error-box" style="display: none;" class="error"></div>
|
|
||||||
|
|
||||||
<div id="config-box" style="display: none; margin-top: 20px;">
|
|
||||||
<h3>Product Configuration</h3>
|
|
||||||
<pre id="config-display" style="background: #f5f5f5; padding: 15px; border-radius: 4px; overflow-x: auto;"></pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd;">
|
|
||||||
<h3>API Endpoints Used</h3>
|
|
||||||
<ul style="line-height: 2;">
|
|
||||||
<li><code>GET /api/product-image/{product}?color={color}&hinge={hinge}</code></li>
|
|
||||||
<li><code>GET /api/product-image/{product}?color={color}&hinge={hinge}&format=json</code></li>
|
|
||||||
<li><code>GET /api/product-config/{product}</code></li>
|
|
||||||
<li><code>POST /api/clear-image-cache</code></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Initialize on load
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
updateImages();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auto-update when controls change
|
|
||||||
document.getElementById('product-select').addEventListener('change', updateImages);
|
|
||||||
document.getElementById('color-select').addEventListener('change', updateImages);
|
|
||||||
document.querySelectorAll('[name="hinge"]').forEach(radio => {
|
|
||||||
radio.addEventListener('change', updateImages);
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateImages() {
|
|
||||||
const product = document.getElementById('product-select').value;
|
|
||||||
const color = document.getElementById('color-select').value;
|
|
||||||
const hinge = document.querySelector('[name="hinge"]:checked').value;
|
|
||||||
const useCache = document.getElementById('cache-checkbox').checked;
|
|
||||||
|
|
||||||
hideError();
|
|
||||||
|
|
||||||
// Method 1: Direct image URL
|
|
||||||
updateDirectImage(product, color, hinge, useCache);
|
|
||||||
|
|
||||||
// Method 2: JSON with base64
|
|
||||||
updateJsonImage(product, color, hinge, useCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDirectImage(product, color, hinge, useCache) {
|
|
||||||
const url = `/api/product-image/${product}?color=${color}&hinge=${hinge}&cache=${useCache}`;
|
|
||||||
|
|
||||||
const img = document.getElementById('direct-image');
|
|
||||||
img.src = url;
|
|
||||||
|
|
||||||
document.getElementById('direct-url').textContent = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateJsonImage(product, color, hinge, useCache) {
|
|
||||||
const url = `/api/product-image/${product}?color=${color}&hinge=${hinge}&format=json&cache=${useCache}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url);
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
document.getElementById('json-image').src = data.image;
|
|
||||||
document.getElementById('json-url').textContent = url;
|
|
||||||
} else {
|
|
||||||
showError(`Error: ${data.error || 'Unknown error'}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError(`Fetch error: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getConfig() {
|
|
||||||
const product = document.getElementById('product-select').value;
|
|
||||||
const url = `/api/product-config/${product}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url);
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
document.getElementById('config-display').textContent =
|
|
||||||
JSON.stringify(data.config, null, 2);
|
|
||||||
document.getElementById('config-box').style.display = 'block';
|
|
||||||
} else {
|
|
||||||
showError(`Error: ${data.error || 'Unknown error'}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError(`Fetch error: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function clearCache() {
|
|
||||||
const product = document.getElementById('product-select').value;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/clear-image-cache', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ productCode: product })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
alert(data.message);
|
|
||||||
updateImages();
|
|
||||||
} else {
|
|
||||||
showError(`Error: ${data.error || 'Unknown error'}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError(`Fetch error: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(message) {
|
|
||||||
const errorBox = document.getElementById('error-box');
|
|
||||||
errorBox.textContent = message;
|
|
||||||
errorBox.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideError() {
|
|
||||||
document.getElementById('error-box').style.display = 'none';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Product Details Form</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background-color: white;
|
|
||||||
padding: 20px;
|
|
||||||
border: 2px solid #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"],
|
|
||||||
input[type="number"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #333;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #333;
|
|
||||||
font-size: 14px;
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
background-color: #d0d0ff;
|
|
||||||
border: 1px solid #333;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #b0b0ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.specifications-section {
|
|
||||||
border: 3px solid #4CAF50;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
background-color: #f0fff0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.specifications-section.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conditional-field {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conditional-field.visible {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #333;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spec-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 120px 1fr;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spec-row label {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spec-row input {
|
|
||||||
border: 1px solid #333;
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.questions-section {
|
|
||||||
border: 1px solid #333;
|
|
||||||
padding: 15px;
|
|
||||||
text-align: center;
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.questions-section p {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.questions-section .placeholder {
|
|
||||||
font-style: italic;
|
|
||||||
color: #666;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Type (door, window, other)</label>
|
|
||||||
<select id="typeSelect" onchange="toggleOtherField()">
|
|
||||||
<option value="">-- Select Type --</option>
|
|
||||||
<option value="door">Door</option>
|
|
||||||
<option value="window">Window</option>
|
|
||||||
<option value="other">Other</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group conditional-field" id="otherField">
|
|
||||||
<label>Please specify</label>
|
|
||||||
<input type="text" placeholder="Enter type details">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Description</label>
|
|
||||||
<textarea placeholder="textfield"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Notes</label>
|
|
||||||
<textarea placeholder="textfield"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button onclick="toggleSpecifications()">Match and Review</button>
|
|
||||||
|
|
||||||
<div class="specifications-section hidden" id="specificationsSection">
|
|
||||||
<div class="spec-row">
|
|
||||||
<label>Product Code:</label>
|
|
||||||
<input type="text" placeholder="" readonly>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spec-row">
|
|
||||||
<label>Width:</label>
|
|
||||||
<input type="text" placeholder="">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spec-row">
|
|
||||||
<label>Height</label>
|
|
||||||
<input type="text" placeholder="">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spec-row">
|
|
||||||
<label>Color</label>
|
|
||||||
<input type="text" placeholder="">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spec-row">
|
|
||||||
<label>Frame:</label>
|
|
||||||
<input type="text" placeholder="">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spec-row">
|
|
||||||
<label>Screen:</label>
|
|
||||||
<input type="text" placeholder="">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spec-row">
|
|
||||||
<label>Etc...</label>
|
|
||||||
<input type="text" placeholder="">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Quantity</label>
|
|
||||||
<input type="number" min="1" value="1" placeholder="">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button>Confirm and Add</button>
|
|
||||||
|
|
||||||
<div class="questions-section">
|
|
||||||
<p>Possible questions for customers based on what is selected or missing</p>
|
|
||||||
<div class="placeholder">[list - checkbox per question]</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function toggleSpecifications() {
|
|
||||||
const section = document.getElementById('specificationsSection');
|
|
||||||
section.classList.toggle('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleOtherField() {
|
|
||||||
const typeSelect = document.getElementById('typeSelect');
|
|
||||||
const otherField = document.getElementById('otherField');
|
|
||||||
|
|
||||||
if (typeSelect.value === 'other') {
|
|
||||||
otherField.classList.add('visible');
|
|
||||||
} else {
|
|
||||||
otherField.classList.remove('visible');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Product Finder</title>
|
|
||||||
<link rel="stylesheet" href="css/styles.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>Product Finder</h1>
|
|
||||||
<div class="user-info-bar" id="userInfoBar">
|
|
||||||
<span id="userLocationInfo"></span>
|
|
||||||
<button onclick="logout()" class="logout-btn">Logout</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="breadcrumb" id="breadcrumb">
|
|
||||||
Start
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="content">
|
|
||||||
<!-- Content will be dynamically loaded here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Base URL for API calls (supports subdirectory deployments)
|
|
||||||
const BASE_URL = '{{ base_url }}';
|
|
||||||
|
|
||||||
// Load user session info
|
|
||||||
async function loadUserInfo() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(BASE_URL + '/api/session');
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
const locationNames = {
|
|
||||||
'LINDS': 'Lindsborg',
|
|
||||||
'IOLA': 'Iola',
|
|
||||||
'KC': 'KC',
|
|
||||||
'BMD': 'BMD'
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentLoc = locationNames[data.currentLocation] || data.currentLocation;
|
|
||||||
document.getElementById('userLocationInfo').textContent =
|
|
||||||
`👤 ${data.username} | 📍 ${currentLoc}`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading user info:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
if (confirm('Are you sure you want to log out?')) {
|
|
||||||
try {
|
|
||||||
await fetch(BASE_URL + '/api/logout', { method: 'POST' });
|
|
||||||
window.location.href = BASE_URL + '/login?message=' + encodeURIComponent('Successfully logged out');
|
|
||||||
} catch (error) {
|
|
||||||
window.location.href = BASE_URL + '/login';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load user info when page loads
|
|
||||||
loadUserInfo();
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
// Pass BASE_URL to script.js by setting it as a window variable
|
|
||||||
window.APP_BASE_URL = BASE_URL;
|
|
||||||
</script>
|
|
||||||
<script src="{{ url_for('serve_js', filename='script.js') }}"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -3,13 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Login - CGW Product Finder</title>
|
<title>Login</title>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
@@ -113,27 +109,6 @@
|
|||||||
border-left: 4px solid #f56565;
|
border-left: 4px solid #f56565;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-info {
|
|
||||||
background: #bee3f8;
|
|
||||||
color: #2c5282;
|
|
||||||
border-left: 4px solid #4299e1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links a {
|
|
||||||
color: #667eea;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
border: 3px solid #f3f3f3;
|
border: 3px solid #f3f3f3;
|
||||||
border-top: 3px solid #667eea;
|
border-top: 3px solid #667eea;
|
||||||
@@ -156,7 +131,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<div class="login-header">
|
<div class="login-header">
|
||||||
<h1>🔐 CGW Product Finder</h1>
|
<h1>🔐 Login</h1>
|
||||||
<p>Please sign in to continue</p>
|
<p>Please sign in to continue</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -180,16 +155,9 @@
|
|||||||
<span class="loading-spinner" id="loadingSpinner"></span>
|
<span class="loading-spinner" id="loadingSpinner"></span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="footer-links">
|
|
||||||
<a href="{{ url_for('user_manager') }}">User Management</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Base URL for API calls (supports subdirectory deployments)
|
|
||||||
const BASE_URL = '{{ base_url }}';
|
|
||||||
|
|
||||||
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -198,12 +166,11 @@
|
|||||||
const loginBtn = document.getElementById('loginBtn');
|
const loginBtn = document.getElementById('loginBtn');
|
||||||
const spinner = document.getElementById('loadingSpinner');
|
const spinner = document.getElementById('loadingSpinner');
|
||||||
|
|
||||||
// Disable button and show spinner
|
|
||||||
loginBtn.disabled = true;
|
loginBtn.disabled = true;
|
||||||
spinner.style.display = 'inline-block';
|
spinner.style.display = 'inline-block';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(BASE_URL + '/api/login', {
|
const response = await fetch('/api/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -216,11 +183,9 @@
|
|||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
// Check if user needs to select a location
|
// Check if user needs to select a location
|
||||||
if (data.requiresLocationSelection) {
|
if (data.requiresLocationSelection) {
|
||||||
// Redirect to location selection page
|
window.location.href = '{{ url_for("auth.select_location_page") }}';
|
||||||
window.location.href = '/select-location';
|
|
||||||
} else {
|
} else {
|
||||||
// Redirect to main app
|
window.location.href = '{{ url_for("products.product_finder") }}';
|
||||||
window.location.href = '/';
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showAlert(data.message || 'Invalid username or password');
|
showAlert(data.message || 'Invalid username or password');
|
||||||
@@ -243,13 +208,6 @@
|
|||||||
alert.classList.remove('show');
|
alert.classList.remove('show');
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there's a message in URL (e.g., after logout)
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const message = urlParams.get('message');
|
|
||||||
if (message) {
|
|
||||||
showAlert(message);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||||
|
<meta http-equiv="Pragma" content="no-cache">
|
||||||
|
<meta http-equiv="Expires" content="0">
|
||||||
|
<title>Product Finder</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('products.serve_css', filename='styles.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>Product Finder</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-info-bar" id="userInfoBar">
|
||||||
|
<span id="userLocationInfo"></span>
|
||||||
|
<a href="{{ url_for('auth.logout') }}" class="logout-btn" title="Logout"
|
||||||
|
onclick="return confirm('Are you sure you want to log out?');"
|
||||||
|
style="background: none; border: none; cursor: pointer; font-size: 16px; text-decoration: none; display: inline-block; padding: 8px 12px;">🚪</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="breadcrumb" id="breadcrumb">
|
||||||
|
Start
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
<!-- Content will be dynamically loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Base URL for API calls
|
||||||
|
const BASE_URL = '';
|
||||||
|
|
||||||
|
// Load user session info
|
||||||
|
async function loadUserInfo() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/session');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
const locationNames = {
|
||||||
|
'LINDS': 'Lindsborg',
|
||||||
|
'IOLA': 'Iola',
|
||||||
|
'KC': 'Kansas City',
|
||||||
|
'BMD': 'BMD'
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentLoc = locationNames[data.currentLocation] || data.currentLocation;
|
||||||
|
const canChangeLocation = data.accessibleLocations && data.accessibleLocations.length > 1;
|
||||||
|
|
||||||
|
let locationDisplay;
|
||||||
|
if (canChangeLocation) {
|
||||||
|
locationDisplay = `<a href="{{ url_for('auth.select_location_page') }}" style="color: #6a4c93; text-decoration: none; font-weight: 500; cursor: pointer;"
|
||||||
|
onmouseover="this.style.textDecoration='underline'"
|
||||||
|
onmouseout="this.style.textDecoration='none'"
|
||||||
|
title="Click to change location">📍 ${currentLoc}</a>`;
|
||||||
|
} else {
|
||||||
|
locationDisplay = `📍 ${currentLoc}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('userLocationInfo').innerHTML =
|
||||||
|
`👤 ${data.username} | ${locationDisplay}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading user info:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load user info when page loads
|
||||||
|
loadUserInfo();
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Pass BASE_URL to script.js by setting it as a window variable
|
||||||
|
window.APP_BASE_URL = '';
|
||||||
|
// Set data path for the quiz
|
||||||
|
window.DATA_PATH = '{{ url_for("products.serve_data", filename="") }}';
|
||||||
|
</script>
|
||||||
|
<script src="{{ url_for('products.serve_js', filename='script.js') }}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+188
-172
@@ -3,13 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Select Location - CGW Product Finder</title>
|
<title>Select Location</title>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
@@ -43,72 +39,75 @@
|
|||||||
|
|
||||||
.location-header p {
|
.location-header p {
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
background: #f7fafc;
|
background: #f7fafc;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 30px;
|
||||||
border-left: 4px solid #667eea;
|
border-left: 4px solid #667eea;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info p {
|
.user-info strong {
|
||||||
color: #333;
|
color: #333;
|
||||||
|
display: block;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info strong {
|
.user-info span {
|
||||||
color: #667eea;
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-list {
|
.location-grid {
|
||||||
margin-bottom: 25px;
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-item {
|
.location-card {
|
||||||
background: #f7fafc;
|
background: #f7fafc;
|
||||||
padding: 15px 20px;
|
border: 3px solid #e0e0e0;
|
||||||
border-radius: 5px;
|
border-radius: 8px;
|
||||||
margin-bottom: 10px;
|
padding: 30px 20px;
|
||||||
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
border: 2px solid transparent;
|
position: relative;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-item:hover {
|
.location-card:hover {
|
||||||
background: #e6f2ff;
|
|
||||||
border-color: #667eea;
|
border-color: #667eea;
|
||||||
transform: translateX(5px);
|
background: #edf2f7;
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-item input[type="radio"] {
|
.location-card.selected {
|
||||||
margin-right: 15px;
|
border-color: #667eea;
|
||||||
width: 20px;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
height: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-item label {
|
|
||||||
cursor: pointer;
|
|
||||||
flex: 1;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-badge {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
color: white;
|
||||||
padding: 4px 12px;
|
}
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 12px;
|
.location-card .icon {
|
||||||
|
font-size: 40px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-card .name {
|
||||||
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.location-card .description {
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 5px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
@@ -134,30 +133,6 @@
|
|||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-logout {
|
|
||||||
background: #f56565;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-logout:hover {
|
|
||||||
background: #e53e3e;
|
|
||||||
box-shadow: 0 5px 15px rgba(245, 101, 101, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-admin {
|
|
||||||
background: #48bb78;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-admin:hover {
|
|
||||||
background: #38a169;
|
|
||||||
box-shadow: 0 5px 15px rgba(72, 187, 120, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-admin.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
@@ -174,145 +149,194 @@
|
|||||||
color: #742a2a;
|
color: #742a2a;
|
||||||
border-left: 4px solid #f56565;
|
border-left: 4px solid #f56565;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #667eea;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.location-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="location-container">
|
<div class="location-container">
|
||||||
<div class="location-header">
|
<div class="location-header">
|
||||||
<h1>📍 Select Your Location</h1>
|
<h1>📍 Select Your Location</h1>
|
||||||
<p>Choose a location to access the Product Finder</p>
|
<p>Choose which location you'll be working from today</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="user-info" id="userInfo">
|
<div class="user-info">
|
||||||
<p><strong>Welcome:</strong> <span id="userName"></span></p>
|
<strong>Welcome, <span id="username">User</span>!</strong>
|
||||||
<p><strong>Default Location:</strong> <span id="defaultLocation"></span></p>
|
<span>Default Location: <span id="defaultLocation">-</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-error" id="errorAlert"></div>
|
<div class="alert alert-error" id="errorAlert"></div>
|
||||||
|
|
||||||
<form id="locationForm">
|
<div class="location-grid" id="locationGrid">
|
||||||
<div class="location-list" id="locationList">
|
|
||||||
<!-- Locations will be populated here -->
|
<!-- Locations will be populated here -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn" id="continueBtn">
|
<button class="btn" id="confirmBtn" disabled>
|
||||||
Continue to Product Finder
|
Confirm Location
|
||||||
</button>
|
<span class="loading-spinner" id="loadingSpinner"></span>
|
||||||
</form>
|
|
||||||
|
|
||||||
<button class="btn btn-admin hidden" id="userManagementBtn" onclick="goToUserManagement()">
|
|
||||||
⚙️ User Management
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="btn btn-logout" onclick="logout()">
|
<div class="footer">
|
||||||
Sign Out
|
<a href="{{ url_for('users.user_manager') }}" id="manageUsersLink" style="display: none; margin-right: 15px;">Manage Users</a>
|
||||||
</button>
|
<a href="{{ url_for('auth.logout') }}">Logout</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Base URL for API calls (supports subdirectory deployments)
|
let selectedLocation = null;
|
||||||
const BASE_URL = '{{ base_url }}';
|
|
||||||
|
|
||||||
const LOCATION_NAMES = {
|
|
||||||
'LINDS': 'Lindsborg',
|
|
||||||
'IOLA': 'Iola',
|
|
||||||
'KC': 'KC',
|
|
||||||
'BMD': 'BMD'
|
|
||||||
};
|
|
||||||
|
|
||||||
let sessionData = null;
|
let sessionData = null;
|
||||||
|
|
||||||
async function loadLocationData() {
|
// Location details
|
||||||
|
const locationDetails = {
|
||||||
|
'LINDS': {
|
||||||
|
icon: '🏭',
|
||||||
|
name: 'LINDS',
|
||||||
|
description: 'Lindsborg Location'
|
||||||
|
},
|
||||||
|
'IOLA': {
|
||||||
|
icon: '🏢',
|
||||||
|
name: 'IOLA',
|
||||||
|
description: 'Iola Location'
|
||||||
|
},
|
||||||
|
'KC': {
|
||||||
|
icon: '🌆',
|
||||||
|
name: 'KC',
|
||||||
|
description: 'Kansas City Location'
|
||||||
|
},
|
||||||
|
'BMD': {
|
||||||
|
icon: '🏛️',
|
||||||
|
name: 'BMD',
|
||||||
|
description: 'BMD Location'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load session data on page load
|
||||||
|
loadSessionData();
|
||||||
|
|
||||||
|
async function loadSessionData() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(BASE_URL + '/api/session');
|
const response = await fetch('/api/session');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
sessionData = data;
|
sessionData = data;
|
||||||
|
document.getElementById('username').textContent = data.username;
|
||||||
|
document.getElementById('defaultLocation').textContent = data.defaultLocation || 'Not set';
|
||||||
|
|
||||||
// Populate user info
|
// Show manage users link if user has permission
|
||||||
document.getElementById('userName').textContent = data.username;
|
if (data.permissions && data.permissions.manage_users === true) {
|
||||||
document.getElementById('defaultLocation').textContent =
|
document.getElementById('manageUsersLink').style.display = 'inline';
|
||||||
LOCATION_NAMES[data.defaultLocation] || data.defaultLocation;
|
}
|
||||||
|
|
||||||
// Populate location list
|
// Render accessible locations
|
||||||
const locationList = document.getElementById('locationList');
|
renderLocations(data.accessibleLocations || []);
|
||||||
const locations = data.accessibleLocations || [];
|
|
||||||
|
|
||||||
if (locations.length === 0) {
|
// Auto-select default location if available
|
||||||
showAlert('No accessible locations found. Please contact an administrator.');
|
if (data.defaultLocation && data.accessibleLocations.includes(data.defaultLocation)) {
|
||||||
document.getElementById('continueBtn').disabled = true;
|
selectLocation(data.defaultLocation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showError('Error loading session. Please login again.');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '{{ url_for("auth.login_page") }}';
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError('Error connecting to server.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLocations(accessibleLocations) {
|
||||||
|
const grid = document.getElementById('locationGrid');
|
||||||
|
|
||||||
|
if (accessibleLocations.length === 0) {
|
||||||
|
grid.innerHTML = '<p style="color: #666; text-align: center; grid-column: 1 / -1;">No accessible locations found.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
locationList.innerHTML = locations.map((code, index) => {
|
grid.innerHTML = accessibleLocations.map(locCode => {
|
||||||
const isDefault = code === data.defaultLocation;
|
const loc = locationDetails[locCode] || {
|
||||||
const checked = isDefault ? 'checked' : '';
|
icon: '📍',
|
||||||
|
name: locCode,
|
||||||
|
description: locCode
|
||||||
|
};
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="location-item" onclick="selectLocation('${code}')">
|
<div class="location-card" onclick="selectLocation('${locCode}')" data-location="${locCode}">
|
||||||
<input type="radio" name="location" value="${code}"
|
<div class="icon">${loc.icon}</div>
|
||||||
id="loc_${code}" ${checked}>
|
<div class="name">${loc.name}</div>
|
||||||
<label for="loc_${code}">
|
<div class="description">${loc.description}</div>
|
||||||
${LOCATION_NAMES[code] || code}
|
|
||||||
</label>
|
|
||||||
${isDefault ? '<span class="location-badge">Default</span>' : ''}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
} else {
|
|
||||||
// Not logged in or session expired
|
|
||||||
window.location.href = BASE_URL + '/login?message=' + encodeURIComponent('Please log in first');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user has manage_users permission
|
function selectLocation(locCode) {
|
||||||
await checkUserManagementPermission();
|
selectedLocation = locCode;
|
||||||
} catch (error) {
|
|
||||||
showAlert('Error loading location data. Please try again.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkUserManagementPermission() {
|
// Remove selected class from all cards
|
||||||
try {
|
document.querySelectorAll('.location-card').forEach(card => {
|
||||||
const response = await fetch(BASE_URL + '/api/check-permission', {
|
card.classList.remove('selected');
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ permission: 'manage_users' })
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
// Add selected class to clicked card
|
||||||
|
const card = document.querySelector(`[data-location="${locCode}"]`);
|
||||||
if (data.status === 'success' && data.hasPermission) {
|
if (card) {
|
||||||
document.getElementById('userManagementBtn').classList.remove('hidden');
|
card.classList.add('selected');
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Permission check failed, keep button hidden
|
|
||||||
console.error('Error checking permission:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToUserManagement() {
|
// Enable confirm button
|
||||||
window.location.href = BASE_URL + '/users';
|
document.getElementById('confirmBtn').disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectLocation(code) {
|
document.getElementById('confirmBtn').addEventListener('click', async () => {
|
||||||
document.getElementById(`loc_${code}`).checked = true;
|
if (!selectedLocation) return;
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('locationForm').addEventListener('submit', async (e) => {
|
const confirmBtn = document.getElementById('confirmBtn');
|
||||||
e.preventDefault();
|
const spinner = document.getElementById('loadingSpinner');
|
||||||
|
|
||||||
const selectedLocation = document.querySelector('input[name="location"]:checked')?.value;
|
confirmBtn.disabled = true;
|
||||||
|
spinner.style.display = 'inline-block';
|
||||||
if (!selectedLocation) {
|
|
||||||
showAlert('Please select a location');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(BASE_URL + '/api/select-location', {
|
const response = await fetch('/api/select-location', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -323,26 +347,21 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
// Redirect to main app
|
// Redirect to quiz page
|
||||||
window.location.href = BASE_URL + '/';
|
window.location.href = '{{ url_for("products.product_finder") }}';
|
||||||
} else {
|
} else {
|
||||||
showAlert(data.message || 'Error selecting location');
|
showError(data.message || 'Error selecting location');
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
spinner.style.display = 'none';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showAlert('Error connecting to server. Please try again.');
|
showError('Error connecting to server');
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
spinner.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function logout() {
|
function showError(message) {
|
||||||
try {
|
|
||||||
await fetch(BASE_URL + '/api/logout', { method: 'POST' });
|
|
||||||
window.location.href = BASE_URL + '/login?message=' + encodeURIComponent('Successfully logged out');
|
|
||||||
} catch (error) {
|
|
||||||
window.location.href = BASE_URL + '/login';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showAlert(message) {
|
|
||||||
const alert = document.getElementById('errorAlert');
|
const alert = document.getElementById('errorAlert');
|
||||||
alert.textContent = message;
|
alert.textContent = message;
|
||||||
alert.classList.add('show');
|
alert.classList.add('show');
|
||||||
@@ -351,9 +370,6 @@
|
|||||||
alert.classList.remove('show');
|
alert.classList.remove('show');
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load location data on page load
|
|
||||||
loadLocationData();
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+265
-713
File diff suppressed because it is too large
Load Diff
-228
@@ -1,228 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Comprehensive app test script
|
|
||||||
Run this through control panel to diagnose issues
|
|
||||||
Writes detailed logs to test_app.log
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
# Setup logging
|
|
||||||
log_file = os.path.join(os.path.dirname(__file__), 'test_app.log')
|
|
||||||
|
|
||||||
def log(message, also_print=True):
|
|
||||||
"""Write to log file and optionally print"""
|
|
||||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
full_message = f"[{timestamp}] {message}"
|
|
||||||
|
|
||||||
with open(log_file, 'a') as f:
|
|
||||||
f.write(full_message + '\n')
|
|
||||||
|
|
||||||
if also_print:
|
|
||||||
print(full_message)
|
|
||||||
|
|
||||||
try:
|
|
||||||
log("="*70)
|
|
||||||
log("APP TEST SCRIPT - START")
|
|
||||||
log("="*70)
|
|
||||||
|
|
||||||
# 1. Environment Info
|
|
||||||
log("\n1. PYTHON ENVIRONMENT")
|
|
||||||
log(f" Python: {sys.version}")
|
|
||||||
log(f" Executable: {sys.executable}")
|
|
||||||
log(f" CWD: {os.getcwd()}")
|
|
||||||
|
|
||||||
# 2. Check environment variables
|
|
||||||
log("\n2. ENVIRONMENT VARIABLES")
|
|
||||||
log(f" APPLICATION_ROOT: {os.environ.get('APPLICATION_ROOT', 'NOT SET')}")
|
|
||||||
log(f" FLASK_ENV: {os.environ.get('FLASK_ENV', 'NOT SET')}")
|
|
||||||
log(f" SECRET_KEY: {'SET' if os.environ.get('SECRET_KEY') else 'NOT SET'}")
|
|
||||||
|
|
||||||
# 3. Check critical files
|
|
||||||
log("\n3. FILE STRUCTURE CHECK")
|
|
||||||
files_to_check = [
|
|
||||||
'app.py',
|
|
||||||
'passenger_wsgi.py',
|
|
||||||
'config.py',
|
|
||||||
'data/users.json',
|
|
||||||
'templates/login.html',
|
|
||||||
'templates/index2.html',
|
|
||||||
'templates/select_location.html',
|
|
||||||
'templates/user_manager.html'
|
|
||||||
]
|
|
||||||
|
|
||||||
all_files_exist = True
|
|
||||||
for file_path in files_to_check:
|
|
||||||
full_path = os.path.join(os.path.dirname(__file__), file_path)
|
|
||||||
exists = os.path.exists(full_path)
|
|
||||||
status = "✓" if exists else "✗ MISSING"
|
|
||||||
log(f" {status} {file_path}")
|
|
||||||
if not exists:
|
|
||||||
all_files_exist = False
|
|
||||||
|
|
||||||
if not all_files_exist:
|
|
||||||
log("\n⚠️ CRITICAL: Missing files detected!")
|
|
||||||
|
|
||||||
# 4. Check dependencies
|
|
||||||
log("\n4. PYTHON PACKAGES")
|
|
||||||
packages = [
|
|
||||||
('flask', 'Flask'),
|
|
||||||
('werkzeug', 'Werkzeug')
|
|
||||||
]
|
|
||||||
|
|
||||||
all_packages_ok = True
|
|
||||||
for module_name, display_name in packages:
|
|
||||||
try:
|
|
||||||
module = __import__(module_name)
|
|
||||||
try:
|
|
||||||
from importlib.metadata import version
|
|
||||||
ver = version(module_name)
|
|
||||||
except:
|
|
||||||
ver = getattr(module, '__version__', 'unknown')
|
|
||||||
log(f" ✓ {display_name}: {ver}")
|
|
||||||
except ImportError as e:
|
|
||||||
log(f" ✗ {display_name}: NOT INSTALLED ({e})")
|
|
||||||
all_packages_ok = False
|
|
||||||
|
|
||||||
if not all_packages_ok:
|
|
||||||
log("\n⚠️ CRITICAL: Missing packages! Run: pip install Flask==3.0.0 Werkzeug==3.0.1")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# 5. Try importing app
|
|
||||||
log("\n5. APP IMPORT TEST")
|
|
||||||
log(" Attempting to import Flask app...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Make sure current directory is in path
|
|
||||||
current_dir = os.path.dirname(__file__)
|
|
||||||
if current_dir not in sys.path:
|
|
||||||
sys.path.insert(0, current_dir)
|
|
||||||
|
|
||||||
from app import app
|
|
||||||
log(" ✓ Flask app imported successfully!")
|
|
||||||
|
|
||||||
# Check app config
|
|
||||||
log("\n6. APP CONFIGURATION")
|
|
||||||
log(f" APPLICATION_ROOT: {app.config.get('APPLICATION_ROOT', 'NOT SET')}")
|
|
||||||
log(f" DEBUG: {app.config.get('DEBUG')}")
|
|
||||||
log(f" SECRET_KEY: {'SET (' + str(len(app.config.get('SECRET_KEY', ''))) + ' chars)' if app.config.get('SECRET_KEY') else 'NOT SET'}")
|
|
||||||
|
|
||||||
# Check if middleware applied
|
|
||||||
log(f" WSGI App Type: {type(app.wsgi_app).__name__}")
|
|
||||||
if 'PrefixMiddleware' in str(type(app.wsgi_app)):
|
|
||||||
log(" ✓ PrefixMiddleware is active")
|
|
||||||
else:
|
|
||||||
log(" ⚠️ PrefixMiddleware NOT active (may cause issues at /product-finder)")
|
|
||||||
|
|
||||||
# Count routes
|
|
||||||
log("\n7. ROUTES CHECK")
|
|
||||||
routes = list(app.url_map.iter_rules())
|
|
||||||
log(f" Total routes: {len(routes)}")
|
|
||||||
|
|
||||||
# Check critical routes
|
|
||||||
critical_routes = ['/login', '/api/login', '/users', '/select-location']
|
|
||||||
log(" Critical routes:")
|
|
||||||
for route_path in critical_routes:
|
|
||||||
found = any(str(rule) == route_path or str(rule).startswith(route_path + '<') for rule in routes)
|
|
||||||
status = "✓" if found else "✗ MISSING"
|
|
||||||
log(f" {status} {route_path}")
|
|
||||||
|
|
||||||
# 8. Test loading users
|
|
||||||
log("\n8. USER DATA TEST")
|
|
||||||
try:
|
|
||||||
users_file = os.path.join(current_dir, 'data', 'users.json')
|
|
||||||
if os.path.exists(users_file):
|
|
||||||
import json
|
|
||||||
with open(users_file, 'r') as f:
|
|
||||||
users = json.load(f)
|
|
||||||
log(f" ✓ Users file loaded: {len(users)} users")
|
|
||||||
log(f" Usernames: {', '.join([u.get('username', '?') for u in users])}")
|
|
||||||
else:
|
|
||||||
log(f" ✗ Users file NOT FOUND at {users_file}")
|
|
||||||
except Exception as e:
|
|
||||||
log(f" ✗ Error loading users: {e}")
|
|
||||||
|
|
||||||
# 9. Test a simple request
|
|
||||||
log("\n9. REQUEST TEST")
|
|
||||||
log(" Testing if app can handle requests...")
|
|
||||||
|
|
||||||
with app.test_client() as client:
|
|
||||||
# Test root redirect
|
|
||||||
try:
|
|
||||||
response = client.get('/')
|
|
||||||
log(f" GET / -> Status: {response.status_code}")
|
|
||||||
if response.status_code == 302:
|
|
||||||
log(f" Redirects to: {response.location}")
|
|
||||||
except Exception as e:
|
|
||||||
log(f" ✗ Error testing /: {e}")
|
|
||||||
|
|
||||||
# Test login page
|
|
||||||
try:
|
|
||||||
response = client.get('/login')
|
|
||||||
log(f" GET /login -> Status: {response.status_code}")
|
|
||||||
if response.status_code != 200:
|
|
||||||
log(f" ⚠️ Login page returned {response.status_code} instead of 200")
|
|
||||||
except Exception as e:
|
|
||||||
log(f" ✗ Error testing /login: {e}")
|
|
||||||
|
|
||||||
# 10. Summary
|
|
||||||
log("\n" + "="*70)
|
|
||||||
log("TEST SUMMARY")
|
|
||||||
log("="*70)
|
|
||||||
|
|
||||||
if all_files_exist and all_packages_ok:
|
|
||||||
log("✓ All files present")
|
|
||||||
log("✓ All packages installed")
|
|
||||||
log("✓ App imports successfully")
|
|
||||||
log(f"✓ {len(routes)} routes registered")
|
|
||||||
|
|
||||||
app_root = app.config.get('APPLICATION_ROOT', '/')
|
|
||||||
if app_root == '/product-finder':
|
|
||||||
log("✓ Configured for /product-finder deployment")
|
|
||||||
else:
|
|
||||||
log(f"⚠️ APPLICATION_ROOT is '{app_root}' (should be '/product-finder' for production)")
|
|
||||||
|
|
||||||
log("\n🎉 APP APPEARS READY!")
|
|
||||||
log("\nIf still getting 500 errors:")
|
|
||||||
log("1. Check that data/users.json exists")
|
|
||||||
log("2. Verify APPLICATION_ROOT env var is set to /product-finder")
|
|
||||||
log("3. Check Apache error log for runtime errors")
|
|
||||||
log("4. Ensure .htaccess is correct")
|
|
||||||
|
|
||||||
else:
|
|
||||||
log("\n⚠️ ISSUES FOUND - Fix these before deployment")
|
|
||||||
|
|
||||||
log("="*70)
|
|
||||||
log(f"\nLog saved to: {log_file}")
|
|
||||||
|
|
||||||
except ImportError as e:
|
|
||||||
log(f" ✗ FAILED to import app!")
|
|
||||||
log(f" Error: {e}")
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
log("\nFull traceback:")
|
|
||||||
log(traceback.format_exc())
|
|
||||||
|
|
||||||
log("\n❌ CRITICAL ERROR: Cannot import Flask app")
|
|
||||||
log("Check that app.py exists and has no syntax errors")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
log(f" ✗ Unexpected error during import!")
|
|
||||||
log(f" Error: {e}")
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
log("\nFull traceback:")
|
|
||||||
log(traceback.format_exc())
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
log(f"\n❌ FATAL ERROR: {e}")
|
|
||||||
import traceback
|
|
||||||
log(traceback.format_exc())
|
|
||||||
|
|
||||||
finally:
|
|
||||||
log("\n" + "="*70)
|
|
||||||
log("TEST COMPLETE")
|
|
||||||
log("="*70)
|
|
||||||
print(f"\n📄 Full log saved to: {log_file}")
|
|
||||||
print("Download this file to see all test results")
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
"""
|
|
||||||
Deployment diagnostic script for /product-finder
|
|
||||||
Run this on the server to verify configuration
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
print("=" * 70)
|
|
||||||
print("CGW PRODUCT FINDER - DEPLOYMENT DIAGNOSTIC")
|
|
||||||
print("=" * 70)
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check Python version
|
|
||||||
print("📌 Python Environment:")
|
|
||||||
print(f" Version: {sys.version}")
|
|
||||||
print(f" Executable: {sys.executable}")
|
|
||||||
print(f" Current Directory: {os.getcwd()}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check environment variable
|
|
||||||
print("📌 Environment Variables:")
|
|
||||||
app_root_env = os.environ.get('APPLICATION_ROOT')
|
|
||||||
if app_root_env:
|
|
||||||
print(f" APPLICATION_ROOT: {app_root_env} ✓")
|
|
||||||
else:
|
|
||||||
print(f" APPLICATION_ROOT: NOT SET ⚠️")
|
|
||||||
print(f" Setting temporarily for test: /product-finder")
|
|
||||||
os.environ['APPLICATION_ROOT'] = '/product-finder'
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Try importing Flask
|
|
||||||
print("📌 Flask Installation:")
|
|
||||||
try:
|
|
||||||
import flask
|
|
||||||
print(f" Flask version: {flask.__version__} ✓")
|
|
||||||
except ImportError as e:
|
|
||||||
print(f" ✗ Flask not installed: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Try importing app
|
|
||||||
print()
|
|
||||||
print("📌 Application Import:")
|
|
||||||
try:
|
|
||||||
from app import app
|
|
||||||
print(f" ✓ App imported successfully")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ✗ Error importing app:")
|
|
||||||
print(f" {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Check app configuration
|
|
||||||
print()
|
|
||||||
print("📌 Application Configuration:")
|
|
||||||
print(f" APPLICATION_ROOT: {app.config.get('APPLICATION_ROOT')}")
|
|
||||||
print(f" SECRET_KEY set: {'Yes' if app.config.get('SECRET_KEY') else 'No'}")
|
|
||||||
print(f" DEBUG mode: {app.config.get('DEBUG')}")
|
|
||||||
print(f" ENV: {app.config.get('ENV', 'not set')}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check middleware
|
|
||||||
print("📌 WSGI Middleware:")
|
|
||||||
wsgi_app_type = type(app.wsgi_app).__name__
|
|
||||||
if 'PrefixMiddleware' in str(type(app.wsgi_app)):
|
|
||||||
print(f" ✓ PrefixMiddleware applied")
|
|
||||||
print(f" Type: {wsgi_app_type}")
|
|
||||||
elif wsgi_app_type == 'Flask':
|
|
||||||
print(f" ⚠️ No middleware applied (running at root)")
|
|
||||||
print(f" Type: {wsgi_app_type}")
|
|
||||||
else:
|
|
||||||
print(f" Middleware type: {wsgi_app_type}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check routes
|
|
||||||
print("📌 Registered Routes:")
|
|
||||||
routes_count = len(list(app.url_map.iter_rules()))
|
|
||||||
print(f" Total routes: {routes_count}")
|
|
||||||
|
|
||||||
# Check critical routes
|
|
||||||
critical_routes = ['/login', '/api/login', '/users', '/api/session']
|
|
||||||
print(f" Critical routes:")
|
|
||||||
for route in critical_routes:
|
|
||||||
found = any(str(rule) == route for rule in app.url_map.iter_rules())
|
|
||||||
status = "✓" if found else "✗"
|
|
||||||
print(f" {status} {route}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check file structure
|
|
||||||
print("📌 File Structure:")
|
|
||||||
critical_files = [
|
|
||||||
'app.py',
|
|
||||||
'passenger_wsgi.py',
|
|
||||||
'config.py',
|
|
||||||
'templates/login.html',
|
|
||||||
'templates/index2.html',
|
|
||||||
'data/users.json'
|
|
||||||
]
|
|
||||||
|
|
||||||
for file_path in critical_files:
|
|
||||||
exists = os.path.exists(file_path)
|
|
||||||
status = "✓" if exists else "✗"
|
|
||||||
print(f" {status} {file_path}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check dependencies
|
|
||||||
print("📌 Python Dependencies:")
|
|
||||||
deps = [
|
|
||||||
('werkzeug', 'Werkzeug'),
|
|
||||||
('flask', 'Flask'),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
from PIL import Image
|
|
||||||
deps.append(('PIL', 'Pillow'))
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for module_name, display_name in deps:
|
|
||||||
try:
|
|
||||||
module = __import__(module_name)
|
|
||||||
version = getattr(module, '__version__', 'unknown')
|
|
||||||
print(f" ✓ {display_name}: {version}")
|
|
||||||
except ImportError:
|
|
||||||
print(f" ✗ {display_name}: NOT INSTALLED")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
print("=" * 70)
|
|
||||||
print("DEPLOYMENT STATUS")
|
|
||||||
print("=" * 70)
|
|
||||||
|
|
||||||
issues = []
|
|
||||||
|
|
||||||
if not os.environ.get('APPLICATION_ROOT'):
|
|
||||||
issues.append("APPLICATION_ROOT not set in environment")
|
|
||||||
|
|
||||||
if app.config.get('APPLICATION_ROOT') == '/':
|
|
||||||
issues.append("APPLICATION_ROOT is '/' but should be '/product-finder' for production")
|
|
||||||
|
|
||||||
if not os.path.exists('templates/login.html'):
|
|
||||||
issues.append("Template files missing")
|
|
||||||
|
|
||||||
if not os.path.exists('data/users.json'):
|
|
||||||
issues.append("User data file missing")
|
|
||||||
|
|
||||||
if issues:
|
|
||||||
print()
|
|
||||||
print("⚠️ ISSUES FOUND:")
|
|
||||||
for issue in issues:
|
|
||||||
print(f" - {issue}")
|
|
||||||
print()
|
|
||||||
print("📝 NEXT STEPS:")
|
|
||||||
print(" 1. Upload missing files to server")
|
|
||||||
print(" 2. Set APPLICATION_ROOT environment variable")
|
|
||||||
print(" 3. Restart Passenger: touch tmp/restart.txt")
|
|
||||||
print(" 4. Check error logs for details")
|
|
||||||
else:
|
|
||||||
print()
|
|
||||||
print("✓ All checks passed!")
|
|
||||||
print()
|
|
||||||
print("🚀 DEPLOYMENT READY")
|
|
||||||
print()
|
|
||||||
print("To restart Passenger:")
|
|
||||||
print(" mkdir -p tmp && touch tmp/restart.txt")
|
|
||||||
print()
|
|
||||||
print("To test:")
|
|
||||||
print(" https://columbiawindows.com/product-finder/")
|
|
||||||
|
|
||||||
print()
|
|
||||||
print("=" * 70)
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
"""
|
|
||||||
Quick test to verify Flask routes are registered correctly
|
|
||||||
Run this from the app folder: python test_routes.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Add current directory to path
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
try:
|
|
||||||
from app import app
|
|
||||||
|
|
||||||
print("=" * 60)
|
|
||||||
print("FLASK ROUTES TEST")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
print("✓ Flask app imported successfully")
|
|
||||||
print()
|
|
||||||
print("Registered routes:")
|
|
||||||
print("-" * 60)
|
|
||||||
|
|
||||||
routes = []
|
|
||||||
for rule in app.url_map.iter_rules():
|
|
||||||
routes.append({
|
|
||||||
'endpoint': rule.endpoint,
|
|
||||||
'methods': ', '.join(sorted(rule.methods - {'HEAD', 'OPTIONS'})),
|
|
||||||
'path': str(rule)
|
|
||||||
})
|
|
||||||
|
|
||||||
# Sort by path
|
|
||||||
routes.sort(key=lambda x: x['path'])
|
|
||||||
|
|
||||||
# Find all login-related routes
|
|
||||||
login_routes = [r for r in routes if 'login' in r['path'].lower() or 'login' in r['endpoint'].lower()]
|
|
||||||
user_routes = [r for r in routes if 'user' in r['path'].lower()]
|
|
||||||
|
|
||||||
print("\n📝 Login & Authentication Routes:")
|
|
||||||
for route in login_routes:
|
|
||||||
print(f" {route['path']:40} [{route['methods']:15}] -> {route['endpoint']}")
|
|
||||||
|
|
||||||
print("\n👥 User Management Routes:")
|
|
||||||
for route in user_routes:
|
|
||||||
print(f" {route['path']:40} [{route['methods']:15}] -> {route['endpoint']}")
|
|
||||||
|
|
||||||
print("\n📊 Total routes registered:", len(routes))
|
|
||||||
print()
|
|
||||||
print("=" * 60)
|
|
||||||
print("✓ All routes loaded successfully!")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
print("To start the server:")
|
|
||||||
print(" python app.py")
|
|
||||||
print()
|
|
||||||
print("Then access:")
|
|
||||||
print(" http://localhost:8080/login")
|
|
||||||
print(" http://localhost:8080/users")
|
|
||||||
print()
|
|
||||||
|
|
||||||
except ImportError as e:
|
|
||||||
print("=" * 60)
|
|
||||||
print("❌ IMPORT ERROR")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
print(f"Failed to import Flask app: {e}")
|
|
||||||
print()
|
|
||||||
print("This might be because:")
|
|
||||||
print(" 1. Flask is not installed: pip install Flask")
|
|
||||||
print(" 2. Werkzeug is not installed: pip install Werkzeug")
|
|
||||||
print(" 3. There's a syntax error in app.py")
|
|
||||||
print()
|
|
||||||
print("Try running:")
|
|
||||||
print(" pip install -r requirements.txt")
|
|
||||||
print()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print("=" * 60)
|
|
||||||
print("❌ ERROR")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
print(f"Error: {e}")
|
|
||||||
print()
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
print()
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Server troubleshooting script for 403/404 errors
|
|
||||||
Run this on the server to diagnose Apache/Passenger issues
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
print("=" * 70)
|
|
||||||
print("TROUBLESHOOTING 403/404 ERRORS")
|
|
||||||
print("=" * 70)
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check current directory
|
|
||||||
current_dir = os.getcwd()
|
|
||||||
print(f"📁 Current directory: {current_dir}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check file permissions
|
|
||||||
print("📌 File Permissions Check:")
|
|
||||||
files_to_check = [
|
|
||||||
'passenger_wsgi.py',
|
|
||||||
'.htaccess',
|
|
||||||
'app.py',
|
|
||||||
'data/users.json',
|
|
||||||
'templates/login.html'
|
|
||||||
]
|
|
||||||
|
|
||||||
for file in files_to_check:
|
|
||||||
if os.path.exists(file):
|
|
||||||
stat_info = os.stat(file)
|
|
||||||
perms = oct(stat_info.st_mode)[-3:]
|
|
||||||
print(f" ✓ {file:<30} Permissions: {perms}")
|
|
||||||
else:
|
|
||||||
print(f" ✗ {file:<30} NOT FOUND")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check directory permissions
|
|
||||||
print("📌 Directory Permissions:")
|
|
||||||
dirs_to_check = ['.', 'templates', 'data', 'static', 'tmp']
|
|
||||||
for dir_path in dirs_to_check:
|
|
||||||
if os.path.exists(dir_path):
|
|
||||||
stat_info = os.stat(dir_path)
|
|
||||||
perms = oct(stat_info.st_mode)[-3:]
|
|
||||||
print(f" ✓ {dir_path:<30} Permissions: {perms}")
|
|
||||||
else:
|
|
||||||
print(f" ⚠️ {dir_path:<30} NOT FOUND (might be OK)")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check for tmp/restart.txt
|
|
||||||
print("📌 Passenger Restart Check:")
|
|
||||||
if os.path.exists('tmp/restart.txt'):
|
|
||||||
from datetime import datetime
|
|
||||||
mtime = os.path.getmtime('tmp/restart.txt')
|
|
||||||
mtime_str = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
print(f" ✓ tmp/restart.txt exists (last modified: {mtime_str})")
|
|
||||||
else:
|
|
||||||
print(f" ✗ tmp/restart.txt NOT FOUND")
|
|
||||||
print(f" Create it with: mkdir -p tmp && touch tmp/restart.txt")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check environment variables
|
|
||||||
print("📌 Environment Variables:")
|
|
||||||
print(f" APPLICATION_ROOT: {os.environ.get('APPLICATION_ROOT', 'NOT SET')}")
|
|
||||||
print(f" HOME: {os.environ.get('HOME', 'NOT SET')}")
|
|
||||||
print(f" USER: {os.environ.get('USER', 'NOT SET')}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Check .htaccess content
|
|
||||||
print("📌 .htaccess Configuration:")
|
|
||||||
if os.path.exists('.htaccess'):
|
|
||||||
print(f" ✓ .htaccess exists")
|
|
||||||
with open('.htaccess', 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
if 'PassengerEnabled' in content:
|
|
||||||
print(f" ✓ PassengerEnabled directive found")
|
|
||||||
else:
|
|
||||||
print(f" ✗ PassengerEnabled directive NOT FOUND")
|
|
||||||
|
|
||||||
if 'PassengerAppRoot' in content:
|
|
||||||
print(f" ✓ PassengerAppRoot directive found")
|
|
||||||
# Extract the path
|
|
||||||
for line in content.split('\n'):
|
|
||||||
if 'PassengerAppRoot' in line:
|
|
||||||
print(f" Value: {line.strip()}")
|
|
||||||
else:
|
|
||||||
print(f" ✗ PassengerAppRoot NOT FOUND")
|
|
||||||
|
|
||||||
if 'PassengerPython' in content:
|
|
||||||
print(f" ✓ PassengerPython directive found")
|
|
||||||
for line in content.split('\n'):
|
|
||||||
if 'PassengerPython' in line:
|
|
||||||
print(f" Value: {line.strip()}")
|
|
||||||
python_path = line.split()[-1] if len(line.split()) > 1 else ''
|
|
||||||
if os.path.exists(python_path):
|
|
||||||
print(f" ✓ Python executable exists")
|
|
||||||
else:
|
|
||||||
print(f" ✗ Python executable NOT FOUND at {python_path}")
|
|
||||||
else:
|
|
||||||
print(f" ✗ .htaccess NOT FOUND")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Try importing Flask
|
|
||||||
print("📌 Flask Import Test:")
|
|
||||||
try:
|
|
||||||
sys.path.insert(0, current_dir)
|
|
||||||
from app import app
|
|
||||||
print(f" ✓ Flask app imported successfully")
|
|
||||||
print(f" Routes registered: {len(list(app.url_map.iter_rules()))}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ✗ Error importing app: {e}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Recommendations
|
|
||||||
print("=" * 70)
|
|
||||||
print("COMMON FIXES FOR 403/404 ERRORS:")
|
|
||||||
print("=" * 70)
|
|
||||||
print()
|
|
||||||
print("1️⃣ RESTART PASSENGER:")
|
|
||||||
print(" mkdir -p tmp")
|
|
||||||
print(" touch tmp/restart.txt")
|
|
||||||
print()
|
|
||||||
print("2️⃣ CHECK FILE PERMISSIONS:")
|
|
||||||
print(" chmod 644 passenger_wsgi.py")
|
|
||||||
print(" chmod 644 .htaccess")
|
|
||||||
print(" chmod 755 .")
|
|
||||||
print()
|
|
||||||
print("3️⃣ VERIFY PATHS IN .htaccess:")
|
|
||||||
print(" PassengerAppRoot should point to: " + current_dir)
|
|
||||||
print()
|
|
||||||
print("4️⃣ CHECK APACHE ERROR LOG:")
|
|
||||||
print(" tail -50 ~/logs/error_log")
|
|
||||||
print(" (Look for Passenger errors)")
|
|
||||||
print()
|
|
||||||
print("5️⃣ VERIFY .htaccess IS BEING READ:")
|
|
||||||
print(" If AllowOverride is not set in Apache config, .htaccess is ignored")
|
|
||||||
print()
|
|
||||||
print("6️⃣ SIMPLER .htaccess (if still failing):")
|
|
||||||
print(" Try creating a minimal .htaccess with just:")
|
|
||||||
print(" PassengerEnabled on")
|
|
||||||
print()
|
|
||||||
print("=" * 70)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
"""
|
|
||||||
WSGI entry point for production deployment
|
|
||||||
Use with Gunicorn: gunicorn wsgi:app
|
|
||||||
"""
|
|
||||||
from app import app
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run()
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Simple Flask Test App
|
|
||||||
|
|
||||||
A minimal 2-page Flask application to test Passenger WSGI deployment.
|
|
||||||
|
|
||||||
## Pages
|
|
||||||
- `/` - Home page (Hello World)
|
|
||||||
- `/about` - About page
|
|
||||||
- `/test` - Test page (shows system info)
|
|
||||||
|
|
||||||
## Local Testing
|
|
||||||
```bash
|
|
||||||
cd app_simple
|
|
||||||
python app.py
|
|
||||||
```
|
|
||||||
Visit: http://localhost:8080/
|
|
||||||
|
|
||||||
## Server Deployment
|
|
||||||
|
|
||||||
### Upload these files to: `/home/bmdwtjuw/product-finder/`
|
|
||||||
- `app.py`
|
|
||||||
- `wsgi.py`
|
|
||||||
- `templates/home.html`
|
|
||||||
- `templates/about.html`
|
|
||||||
|
|
||||||
### Control Panel Configuration
|
|
||||||
- **Application startup file:** `wsgi.py`
|
|
||||||
- **Application Entry point:** `app`
|
|
||||||
- **Python version:** 3.13.11
|
|
||||||
|
|
||||||
### Test URLs (after deployment)
|
|
||||||
- https://columbiawindows.com/product-finder/
|
|
||||||
- https://columbiawindows.com/product-finder/about
|
|
||||||
- https://columbiawindows.com/product-finder/test
|
|
||||||
|
|
||||||
## What This Tests
|
|
||||||
✓ Python 3.13.11 compatibility
|
|
||||||
✓ Flask framework loading
|
|
||||||
✓ Template rendering
|
|
||||||
✓ URL routing
|
|
||||||
✓ Passenger WSGI configuration
|
|
||||||
|
|
||||||
Once this works, you can add back the login/user management features from `app2` folder.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
from flask import Flask, render_template
|
|
||||||
|
|
||||||
# Initialize Flask
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def home():
|
|
||||||
"""Home page - Hello World"""
|
|
||||||
return render_template('home.html')
|
|
||||||
|
|
||||||
@app.route('/about')
|
|
||||||
def about():
|
|
||||||
"""About page"""
|
|
||||||
return render_template('about.html')
|
|
||||||
|
|
||||||
@app.route('/test')
|
|
||||||
def test():
|
|
||||||
"""Test route - no template needed"""
|
|
||||||
import sys
|
|
||||||
return f"""
|
|
||||||
<html>
|
|
||||||
<head><title>Test Page</title></head>
|
|
||||||
<body style="font-family: Arial; padding: 20px;">
|
|
||||||
<h1>Flask App is Running!</h1>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Python:</strong> {sys.version}</li>
|
|
||||||
<li><strong>Flask:</strong> Working</li>
|
|
||||||
<li><strong>Routes:</strong> {len(list(app.url_map.iter_rules()))}</li>
|
|
||||||
</ul>
|
|
||||||
<p><a href="/">Home</a> | <a href="/about">About</a></p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Run locally on port 8080
|
|
||||||
app.run(debug=True, host='0.0.0.0', port=8080)
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Deployment Check Script
|
|
||||||
Run this on the server to verify configuration
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
print("=" * 70)
|
|
||||||
print("DEPLOYMENT CONFIGURATION CHECK")
|
|
||||||
print("=" * 70)
|
|
||||||
|
|
||||||
# Check Python version
|
|
||||||
print(f"\nPython Version: {sys.version}")
|
|
||||||
print(f"Python Executable: {sys.executable}")
|
|
||||||
|
|
||||||
# Check current directory
|
|
||||||
print(f"\nCurrent Directory: {os.getcwd()}")
|
|
||||||
|
|
||||||
# Check for required files
|
|
||||||
print("\n" + "=" * 70)
|
|
||||||
print("CHECKING FILES")
|
|
||||||
print("=" * 70)
|
|
||||||
|
|
||||||
files_to_check = ['app.py', 'wsgi.py', 'templates/home.html', 'templates/about.html']
|
|
||||||
for file in files_to_check:
|
|
||||||
exists = "✓ EXISTS" if os.path.exists(file) else "✗ MISSING"
|
|
||||||
print(f"{exists}: {file}")
|
|
||||||
|
|
||||||
# Check for passenger_wsgi.py (should NOT exist)
|
|
||||||
if os.path.exists('passenger_wsgi.py'):
|
|
||||||
print("✗ WARNING: passenger_wsgi.py exists - DELETE THIS FILE!")
|
|
||||||
else:
|
|
||||||
print("✓ GOOD: passenger_wsgi.py does not exist")
|
|
||||||
|
|
||||||
# Try importing Flask
|
|
||||||
print("\n" + "=" * 70)
|
|
||||||
print("CHECKING FLASK")
|
|
||||||
print("=" * 70)
|
|
||||||
try:
|
|
||||||
import flask
|
|
||||||
print(f"✓ Flask version: {flask.__version__}")
|
|
||||||
except ImportError as e:
|
|
||||||
print(f"✗ Flask not installed: {e}")
|
|
||||||
|
|
||||||
# Try importing the app
|
|
||||||
print("\n" + "=" * 70)
|
|
||||||
print("CHECKING APP IMPORT")
|
|
||||||
print("=" * 70)
|
|
||||||
try:
|
|
||||||
from app import app
|
|
||||||
print("✓ App imported successfully")
|
|
||||||
print(f"✓ App name: {app.name}")
|
|
||||||
print(f"✓ Routes count: {len(list(app.url_map.iter_rules()))}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Error importing app: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
print("\n" + "=" * 70)
|
|
||||||
print("DONE")
|
|
||||||
print("=" * 70)
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Flask==3.0.0
|
|
||||||
Werkzeug==3.0.1
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>About - Simple Flask App</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 50px auto;
|
|
||||||
padding: 20px;
|
|
||||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: 10px;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
font-size: 2.5em;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #ffd700;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.nav {
|
|
||||||
margin-top: 30px;
|
|
||||||
padding-top: 20px;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>About This App</h1>
|
|
||||||
<p>This is a simple 2-page Flask application created to test server deployment configuration.</p>
|
|
||||||
<p><strong>Purpose:</strong> Verify that Passenger WSGI configuration works correctly.</p>
|
|
||||||
|
|
||||||
<h2>Configuration:</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Startup file:</strong> wsgi.py</li>
|
|
||||||
<li><strong>Entry point:</strong> app</li>
|
|
||||||
<li><strong>Framework:</strong> Flask 3.0.0</li>
|
|
||||||
<li><strong>Python:</strong> 3.13.11</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="nav">
|
|
||||||
<a href="/">Home</a> |
|
|
||||||
<a href="/about">About</a> |
|
|
||||||
<a href="/test">Test</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Home - Simple Flask App</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 50px auto;
|
|
||||||
padding: 20px;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: 10px;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
font-size: 2.5em;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #ffd700;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.nav {
|
|
||||||
margin-top: 30px;
|
|
||||||
padding-top: 20px;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>Hello World!</h1>
|
|
||||||
<p>Welcome to the simple Flask application.</p>
|
|
||||||
<p>This is a minimal test to verify the server configuration works correctly.</p>
|
|
||||||
|
|
||||||
<div class="nav">
|
|
||||||
<a href="/">Home</a> |
|
|
||||||
<a href="/about">About</a> |
|
|
||||||
<a href="/test">Test</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
from app import app
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run()
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
from flask import Flask, session, redirect, url_for
|
|
||||||
import secrets
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Get the directory where this script is located
|
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
|
|
||||||
# Initialize Flask with explicit paths
|
|
||||||
app = Flask(__name__,
|
|
||||||
template_folder=os.path.join(basedir, 'templates'),
|
|
||||||
static_folder=os.path.join(basedir, 'static'))
|
|
||||||
|
|
||||||
# Configure session - generate a random secret key
|
|
||||||
app.secret_key = secrets.token_hex(32)
|
|
||||||
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
|
||||||
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
|
||||||
|
|
||||||
# Import and register blueprints
|
|
||||||
from blueprints.auth import auth_bp
|
|
||||||
from blueprints.users import users_bp
|
|
||||||
|
|
||||||
app.register_blueprint(auth_bp)
|
|
||||||
app.register_blueprint(users_bp)
|
|
||||||
|
|
||||||
# Home route
|
|
||||||
@app.route('/')
|
|
||||||
def home():
|
|
||||||
"""Redirect to login if not authenticated, otherwise show home"""
|
|
||||||
if 'user_id' not in session:
|
|
||||||
return redirect(url_for('auth.login_page'))
|
|
||||||
|
|
||||||
# If user hasn't selected a location yet, redirect to selection
|
|
||||||
if not session.get('currentLocation') and len(session.get('accessibleLocations', [])) > 1:
|
|
||||||
return redirect(url_for('auth.select_location_page'))
|
|
||||||
|
|
||||||
return f"""
|
|
||||||
<html>
|
|
||||||
<head><title>Home</title></head>
|
|
||||||
<body style="font-family: Arial; padding: 20px;">
|
|
||||||
<h1>Welcome, {session.get('username', 'User')}!</h1>
|
|
||||||
<p>Current Location: {session.get('currentLocation', 'Not selected')}</p>
|
|
||||||
<ul>
|
|
||||||
<li><a href="{url_for('users.user_manager')}">Manage Users</a></li>
|
|
||||||
<li><a href="{url_for('auth.select_location_page')}">Change Location</a></li>
|
|
||||||
<li><a href="{url_for('auth.logout')}">Logout</a></li>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
@app.route('/test')
|
|
||||||
def test():
|
|
||||||
"""Test route to verify app is running"""
|
|
||||||
import sys
|
|
||||||
return f"""
|
|
||||||
<html>
|
|
||||||
<head><title>Test Page</title></head>
|
|
||||||
<body style="font-family: Arial; padding: 20px;">
|
|
||||||
<h1>Flask App is Running!</h1>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Python:</strong> {sys.version}</li>
|
|
||||||
<li><strong>Routes:</strong> {len(list(app.url_map.iter_rules()))}</li>
|
|
||||||
</ul>
|
|
||||||
<p><a href="{url_for('auth.login_page')}">Login</a></p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Error handlers
|
|
||||||
@app.errorhandler(404)
|
|
||||||
def page_not_found(e):
|
|
||||||
return "<h1>404 - Page Not Found</h1>", 404
|
|
||||||
|
|
||||||
@app.errorhandler(500)
|
|
||||||
def internal_error(e):
|
|
||||||
return "<h1>500 - Internal Server Error</h1>", 500
|
|
||||||
|
|
||||||
# This is the critical line for Passenger
|
|
||||||
application = app
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Create necessary directories
|
|
||||||
os.makedirs(os.path.join(basedir, 'templates'), exist_ok=True)
|
|
||||||
os.makedirs(os.path.join(basedir, 'data'), exist_ok=True)
|
|
||||||
|
|
||||||
# Run the application
|
|
||||||
app.run(debug=True, host='0.0.0.0', port=8080)
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"username": "Master",
|
|
||||||
"password": "pbkdf2:sha256:1000000$tPRmseRGKLveXTxS$87eda9a20db29d12838fcb607f1f3536141f743ec9879702dc2a0dd893b4078d",
|
|
||||||
"defaultLocation": "KC",
|
|
||||||
"permissions": {
|
|
||||||
"manage_users": true
|
|
||||||
},
|
|
||||||
"locationSettings": {
|
|
||||||
"LINDS": {
|
|
||||||
"accessible": true
|
|
||||||
},
|
|
||||||
"IOLA": {
|
|
||||||
"accessible": true
|
|
||||||
},
|
|
||||||
"KC": {
|
|
||||||
"accessible": true
|
|
||||||
},
|
|
||||||
"BMD": {
|
|
||||||
"accessible": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"active": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import importlib.machinery
|
|
||||||
import importlib.util
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(__file__))
|
|
||||||
|
|
||||||
def load_source(modname, filename):
|
|
||||||
loader = importlib.machinery.SourceFileLoader(modname, filename)
|
|
||||||
spec = importlib.util.spec_from_file_location(modname, filename, loader=loader)
|
|
||||||
module = importlib.util.module_from_spec(spec)
|
|
||||||
loader.exec_module(module)
|
|
||||||
return module
|
|
||||||
|
|
||||||
wsgi = load_source('wsgi', 'app.py')
|
|
||||||
application = wsgi.application
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Flask==3.1.3
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Login</title>
|
|
||||||
<style>
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-container {
|
|
||||||
background: white;
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-header h1 {
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-header p {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
border: 2px solid #e0e0e0;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 16px;
|
|
||||||
transition: border-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
width: 100%;
|
|
||||||
padding: 14px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
background: #5568d3;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:disabled {
|
|
||||||
background: #cbd5e0;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert.show {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-error {
|
|
||||||
background: #fed7d7;
|
|
||||||
color: #742a2a;
|
|
||||||
border-left: 4px solid #f56565;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
border: 3px solid #f3f3f3;
|
|
||||||
border-top: 3px solid #667eea;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 10px;
|
|
||||||
vertical-align: middle;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="login-container">
|
|
||||||
<div class="login-header">
|
|
||||||
<h1>🔐 Login</h1>
|
|
||||||
<p>Please sign in to continue</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-error" id="errorAlert"></div>
|
|
||||||
|
|
||||||
<form id="loginForm">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="username">Username</label>
|
|
||||||
<input type="text" id="username" name="username" required autofocus
|
|
||||||
placeholder="Enter your username">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password">Password</label>
|
|
||||||
<input type="password" id="password" name="password" required
|
|
||||||
placeholder="Enter your password">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn" id="loginBtn">
|
|
||||||
Sign In
|
|
||||||
<span class="loading-spinner" id="loadingSpinner"></span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const username = document.getElementById('username').value;
|
|
||||||
const password = document.getElementById('password').value;
|
|
||||||
const loginBtn = document.getElementById('loginBtn');
|
|
||||||
const spinner = document.getElementById('loadingSpinner');
|
|
||||||
|
|
||||||
loginBtn.disabled = true;
|
|
||||||
spinner.style.display = 'inline-block';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ username, password })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
// Check if user needs to select a location
|
|
||||||
if (data.requiresLocationSelection) {
|
|
||||||
window.location.href = '/select-location';
|
|
||||||
} else {
|
|
||||||
window.location.href = '/';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showAlert(data.message || 'Invalid username or password');
|
|
||||||
loginBtn.disabled = false;
|
|
||||||
spinner.style.display = 'none';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showAlert('Error connecting to server. Please try again.');
|
|
||||||
loginBtn.disabled = false;
|
|
||||||
spinner.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function showAlert(message) {
|
|
||||||
const alert = document.getElementById('errorAlert');
|
|
||||||
alert.textContent = message;
|
|
||||||
alert.classList.add('show');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
alert.classList.remove('show');
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,375 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Select Location</title>
|
|
||||||
<style>
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-container {
|
|
||||||
background: white;
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
||||||
width: 100%;
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-header h1 {
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-header p {
|
|
||||||
color: #666;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-info {
|
|
||||||
background: #f7fafc;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
border-left: 4px solid #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-info strong {
|
|
||||||
color: #333;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-info span {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-card {
|
|
||||||
background: #f7fafc;
|
|
||||||
border: 3px solid #e0e0e0;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 30px 20px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-card:hover {
|
|
||||||
border-color: #667eea;
|
|
||||||
background: #edf2f7;
|
|
||||||
transform: translateY(-3px);
|
|
||||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-card.selected {
|
|
||||||
border-color: #667eea;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-card .icon {
|
|
||||||
font-size: 40px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-card .name {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-card .description {
|
|
||||||
font-size: 13px;
|
|
||||||
margin-top: 5px;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
width: 100%;
|
|
||||||
padding: 14px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
background: #5568d3;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:disabled {
|
|
||||||
background: #cbd5e0;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert.show {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-error {
|
|
||||||
background: #fed7d7;
|
|
||||||
color: #742a2a;
|
|
||||||
border-left: 4px solid #f56565;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer a {
|
|
||||||
color: #667eea;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
border: 3px solid #f3f3f3;
|
|
||||||
border-top: 3px solid #667eea;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 10px;
|
|
||||||
vertical-align: middle;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.location-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="location-container">
|
|
||||||
<div class="location-header">
|
|
||||||
<h1>📍 Select Your Location</h1>
|
|
||||||
<p>Choose which location you'll be working from today</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="user-info">
|
|
||||||
<strong>Welcome, <span id="username">User</span>!</strong>
|
|
||||||
<span>Default Location: <span id="defaultLocation">-</span></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-error" id="errorAlert"></div>
|
|
||||||
|
|
||||||
<div class="location-grid" id="locationGrid">
|
|
||||||
<!-- Locations will be populated here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn" id="confirmBtn" disabled>
|
|
||||||
Confirm Location
|
|
||||||
<span class="loading-spinner" id="loadingSpinner"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<a href="/users" id="manageUsersLink" style="display: none; margin-right: 15px;">Manage Users</a>
|
|
||||||
<a href="/logout">Logout</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let selectedLocation = null;
|
|
||||||
let sessionData = null;
|
|
||||||
|
|
||||||
// Location details
|
|
||||||
const locationDetails = {
|
|
||||||
'LINDS': {
|
|
||||||
icon: '🏭',
|
|
||||||
name: 'LINDS',
|
|
||||||
description: 'Lindsborg Location'
|
|
||||||
},
|
|
||||||
'IOLA': {
|
|
||||||
icon: '🏢',
|
|
||||||
name: 'IOLA',
|
|
||||||
description: 'Iola Location'
|
|
||||||
},
|
|
||||||
'KC': {
|
|
||||||
icon: '🌆',
|
|
||||||
name: 'KC',
|
|
||||||
description: 'Kansas City Location'
|
|
||||||
},
|
|
||||||
'BMD': {
|
|
||||||
icon: '🏛️',
|
|
||||||
name: 'BMD',
|
|
||||||
description: 'BMD Location'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load session data on page load
|
|
||||||
loadSessionData();
|
|
||||||
|
|
||||||
async function loadSessionData() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/session');
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
sessionData = data;
|
|
||||||
document.getElementById('username').textContent = data.username;
|
|
||||||
document.getElementById('defaultLocation').textContent = data.defaultLocation || 'Not set';
|
|
||||||
|
|
||||||
// Show manage users link if user has permission
|
|
||||||
if (data.permissions && data.permissions.manage_users === true) {
|
|
||||||
document.getElementById('manageUsersLink').style.display = 'inline';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render accessible locations
|
|
||||||
renderLocations(data.accessibleLocations || []);
|
|
||||||
|
|
||||||
// Auto-select default location if available
|
|
||||||
if (data.defaultLocation && data.accessibleLocations.includes(data.defaultLocation)) {
|
|
||||||
selectLocation(data.defaultLocation);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showError('Error loading session. Please login again.');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError('Error connecting to server.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderLocations(accessibleLocations) {
|
|
||||||
const grid = document.getElementById('locationGrid');
|
|
||||||
|
|
||||||
if (accessibleLocations.length === 0) {
|
|
||||||
grid.innerHTML = '<p style="color: #666; text-align: center; grid-column: 1 / -1;">No accessible locations found.</p>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
grid.innerHTML = accessibleLocations.map(locCode => {
|
|
||||||
const loc = locationDetails[locCode] || {
|
|
||||||
icon: '📍',
|
|
||||||
name: locCode,
|
|
||||||
description: locCode
|
|
||||||
};
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="location-card" onclick="selectLocation('${locCode}')" data-location="${locCode}">
|
|
||||||
<div class="icon">${loc.icon}</div>
|
|
||||||
<div class="name">${loc.name}</div>
|
|
||||||
<div class="description">${loc.description}</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectLocation(locCode) {
|
|
||||||
selectedLocation = locCode;
|
|
||||||
|
|
||||||
// Remove selected class from all cards
|
|
||||||
document.querySelectorAll('.location-card').forEach(card => {
|
|
||||||
card.classList.remove('selected');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add selected class to clicked card
|
|
||||||
const card = document.querySelector(`[data-location="${locCode}"]`);
|
|
||||||
if (card) {
|
|
||||||
card.classList.add('selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable confirm button
|
|
||||||
document.getElementById('confirmBtn').disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('confirmBtn').addEventListener('click', async () => {
|
|
||||||
if (!selectedLocation) return;
|
|
||||||
|
|
||||||
const confirmBtn = document.getElementById('confirmBtn');
|
|
||||||
const spinner = document.getElementById('loadingSpinner');
|
|
||||||
|
|
||||||
confirmBtn.disabled = true;
|
|
||||||
spinner.style.display = 'inline-block';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/select-location', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ location: selectedLocation })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
// Redirect to main app
|
|
||||||
window.location.href = '/';
|
|
||||||
} else {
|
|
||||||
showError(data.message || 'Error selecting location');
|
|
||||||
confirmBtn.disabled = false;
|
|
||||||
spinner.style.display = 'none';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError('Error connecting to server');
|
|
||||||
confirmBtn.disabled = false;
|
|
||||||
spinner.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function showError(message) {
|
|
||||||
const alert = document.getElementById('errorAlert');
|
|
||||||
alert.textContent = message;
|
|
||||||
alert.classList.add('show');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
alert.classList.remove('show');
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,498 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>User Manager</title>
|
|
||||||
<style>
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
|
||||||
margin-bottom: 30px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card h2 {
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #667eea;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input, .form-group select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
border: 2px solid #e0e0e0;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input:focus, .form-group select:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 12px 30px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background: #5568d3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-danger {
|
|
||||||
background: #f56565;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-danger:hover {
|
|
||||||
background: #e53e3e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: #cbd5e0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary:hover {
|
|
||||||
background: #a0aec0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-item {
|
|
||||||
background: #f7fafc;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-left: 4px solid #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-item.inactive {
|
|
||||||
opacity: 0.6;
|
|
||||||
border-left-color: #cbd5e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-info {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-info strong {
|
|
||||||
color: #333;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-info span {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-actions button {
|
|
||||||
padding: 8px 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert.show {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-success {
|
|
||||||
background: #c6f6d5;
|
|
||||||
color: #22543d;
|
|
||||||
border-left: 4px solid #48bb78;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-error {
|
|
||||||
background: #fed7d7;
|
|
||||||
color: #742a2a;
|
|
||||||
border-left: 4px solid #f56565;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active-toggle input {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-checkboxes {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-item input {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>👥 User Manager</h1>
|
|
||||||
<a href="/" style="color: #333; text-decoration: none;">← Back to Home</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-success" id="successAlert"></div>
|
|
||||||
<div class="alert alert-error" id="errorAlert"></div>
|
|
||||||
|
|
||||||
<!-- Add User Form -->
|
|
||||||
<div class="card">
|
|
||||||
<h2>Add New User</h2>
|
|
||||||
<form id="addUserForm">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="newUsername">Username</label>
|
|
||||||
<input type="text" id="newUsername" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="newPassword">Password</label>
|
|
||||||
<input type="password" id="newPassword" required minlength="6">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="defaultLocation">Default Location</label>
|
|
||||||
<select id="defaultLocation" required>
|
|
||||||
<option value="">Select Location</option>
|
|
||||||
<option value="LINDS">LINDS</option>
|
|
||||||
<option value="IOLA">IOLA</option>
|
|
||||||
<option value="KC">KC</option>
|
|
||||||
<option value="BMD">BMD</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Accessible Locations</label>
|
|
||||||
<div class="location-checkboxes" id="accessibleLocations">
|
|
||||||
<div class="location-item">
|
|
||||||
<input type="checkbox" id="loc_LINDS" value="LINDS">
|
|
||||||
<label for="loc_LINDS">LINDS</label>
|
|
||||||
</div>
|
|
||||||
<div class="location-item">
|
|
||||||
<input type="checkbox" id="loc_IOLA" value="IOLA">
|
|
||||||
<label for="loc_IOLA">IOLA</label>
|
|
||||||
</div>
|
|
||||||
<div class="location-item">
|
|
||||||
<input type="checkbox" id="loc_KC" value="KC">
|
|
||||||
<label for="loc_KC">KC</label>
|
|
||||||
</div>
|
|
||||||
<div class="location-item">
|
|
||||||
<input type="checkbox" id="loc_BMD" value="BMD">
|
|
||||||
<label for="loc_BMD">BMD</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Permissions</label>
|
|
||||||
<div class="location-checkboxes">
|
|
||||||
<div class="location-item">
|
|
||||||
<input type="checkbox" id="perm_manage_users" value="manage_users">
|
|
||||||
<label for="perm_manage_users">Manage Users</label>
|
|
||||||
</div>
|
|
||||||
<div class="location-item">
|
|
||||||
<input type="checkbox" id="perm_create_quotes" value="create_quotes">
|
|
||||||
<label for="perm_create_quotes">Create Quotes</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Add User</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- User List -->
|
|
||||||
<div class="card">
|
|
||||||
<h2>Existing Users</h2>
|
|
||||||
<div id="userList"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let users = [];
|
|
||||||
|
|
||||||
// Load users on page load
|
|
||||||
loadUsers();
|
|
||||||
|
|
||||||
async function loadUsers() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/users/api');
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
users = data.users;
|
|
||||||
renderUsers();
|
|
||||||
} else {
|
|
||||||
showError(data.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError('Error loading users: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderUsers() {
|
|
||||||
const userList = document.getElementById('userList');
|
|
||||||
|
|
||||||
if (users.length === 0) {
|
|
||||||
userList.innerHTML = '<p style="color: #666;">No users found. Add a user above.</p>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
userList.innerHTML = users.map((user, index) => `
|
|
||||||
<div class="user-item ${user.active ? '' : 'inactive'}">
|
|
||||||
<div class="user-info">
|
|
||||||
<strong>${user.username}</strong>
|
|
||||||
<span>Location: ${user.defaultLocation || 'Not set'} |
|
|
||||||
Status: ${user.active ? '<span style="color: #48bb78;">Active</span>' : '<span style="color: #f56565;">Inactive</span>'}</span>
|
|
||||||
</div>
|
|
||||||
<div class="user-actions">
|
|
||||||
<div class="active-toggle">
|
|
||||||
<input type="checkbox" ${user.active ? 'checked' : ''}
|
|
||||||
onchange="toggleActive(${index}, this.checked)">
|
|
||||||
<span>Active</span>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-secondary" onclick="changePassword(${index})">Change Password</button>
|
|
||||||
<button class="btn btn-danger" onclick="deleteUser(${index})">Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add user form submission
|
|
||||||
document.getElementById('addUserForm').addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const username = document.getElementById('newUsername').value;
|
|
||||||
const password = document.getElementById('newPassword').value;
|
|
||||||
const defaultLocation = document.getElementById('defaultLocation').value;
|
|
||||||
|
|
||||||
// Get accessible locations
|
|
||||||
const locationSettings = {};
|
|
||||||
['LINDS', 'IOLA', 'KC', 'BMD'].forEach(loc => {
|
|
||||||
const checkbox = document.getElementById(`loc_${loc}`);
|
|
||||||
if (checkbox.checked) {
|
|
||||||
locationSettings[loc] = { accessible: true, permissions: {} };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get permissions
|
|
||||||
const permissions = {};
|
|
||||||
if (document.getElementById('perm_manage_users').checked) {
|
|
||||||
permissions.manage_users = true;
|
|
||||||
}
|
|
||||||
if (document.getElementById('perm_create_quotes').checked) {
|
|
||||||
permissions.create_quotes = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/users/api', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
defaultLocation,
|
|
||||||
locationSettings,
|
|
||||||
permissions,
|
|
||||||
active: true
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
showSuccess('User added successfully!');
|
|
||||||
users = data.users;
|
|
||||||
renderUsers();
|
|
||||||
e.target.reset();
|
|
||||||
} else {
|
|
||||||
showError(data.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError('Error adding user: ' + error.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function toggleActive(index, active) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/users/api/${index}/active`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ active })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
showSuccess('User status updated!');
|
|
||||||
users = data.users;
|
|
||||||
renderUsers();
|
|
||||||
} else {
|
|
||||||
showError(data.message);
|
|
||||||
renderUsers(); // Revert checkbox
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError('Error updating user: ' + error.message);
|
|
||||||
renderUsers(); // Revert checkbox
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function changePassword(index) {
|
|
||||||
const newPassword = prompt('Enter new password (min 6 characters):');
|
|
||||||
if (!newPassword || newPassword.length < 6) {
|
|
||||||
alert('Password must be at least 6 characters long');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentPassword = prompt('Enter YOUR password to confirm this change:');
|
|
||||||
if (!currentPassword) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/users/api/${index}/change-password`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
newPassword: newPassword,
|
|
||||||
currentUserPassword: currentPassword
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
showSuccess(data.message);
|
|
||||||
} else {
|
|
||||||
showError(data.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError('Error changing password: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteUser(index) {
|
|
||||||
if (!confirm(`Are you sure you want to delete ${users[index].username}?`)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/users/api/${index}`, {
|
|
||||||
method: 'DELETE'
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
showSuccess('User deleted successfully!');
|
|
||||||
users = data.users;
|
|
||||||
renderUsers();
|
|
||||||
} else {
|
|
||||||
showError(data.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError('Error deleting user: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSuccess(message) {
|
|
||||||
const alert = document.getElementById('successAlert');
|
|
||||||
alert.textContent = message;
|
|
||||||
alert.classList.add('show');
|
|
||||||
setTimeout(() => alert.classList.remove('show'), 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(message) {
|
|
||||||
const alert = document.getElementById('errorAlert');
|
|
||||||
alert.textContent = message;
|
|
||||||
alert.classList.add('show');
|
|
||||||
setTimeout(() => alert.classList.remove('show'), 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-check location when selected as default
|
|
||||||
document.getElementById('defaultLocation').addEventListener('change', (e) => {
|
|
||||||
const loc = e.target.value;
|
|
||||||
if (loc) {
|
|
||||||
document.getElementById(`loc_${loc}`).checked = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user