Initial commit - QBPOS Help
This commit is contained in:
BIN
python/__pycache__/production_server.cpython-312.pyc
Normal file
BIN
python/__pycache__/production_server.cpython-312.pyc
Normal file
Binary file not shown.
63
python/fix_inline_styles.py
Normal file
63
python/fix_inline_styles.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Remove inline font-size styles from all HTML files in the POS_Help directory.
|
||||
This allows the CSS rules to properly control font sizes.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def remove_inline_font_styles(file_path):
|
||||
"""Remove style="font-size: Xpt;" from HTML files."""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
original_content = content
|
||||
|
||||
# Remove style="font-size: 9pt;" and similar patterns
|
||||
# Pattern matches: style="font-size: XXpt;" or style="font-size:XXpt;"
|
||||
content = re.sub(r'\s+style="font-size:\s*\d+pt;"', '', content)
|
||||
|
||||
# Also handle cases where there might be other styles in the attribute
|
||||
# Just remove the font-size portion
|
||||
content = re.sub(r'(style="[^"]*?)font-size:\s*\d+pt;\s*', r'\1', content)
|
||||
|
||||
# Clean up empty style attributes
|
||||
content = re.sub(r'\s+style=""', '', content)
|
||||
|
||||
if content != original_content:
|
||||
with open(file_path, 'w', encoding='utf-8', errors='ignore') as f:
|
||||
f.write(content)
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error processing {file_path}: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
base_dir = Path('/home/pts/Documents/QBPOS_Help_Web/QB_Help_Web/POS_Help')
|
||||
|
||||
if not base_dir.exists():
|
||||
print(f"Error: Directory {base_dir} not found")
|
||||
return
|
||||
|
||||
# Find all .htm files
|
||||
htm_files = list(base_dir.rglob('*.htm'))
|
||||
|
||||
print(f"Found {len(htm_files)} HTML files")
|
||||
print("Removing inline font-size styles...")
|
||||
|
||||
modified_count = 0
|
||||
for htm_file in htm_files:
|
||||
if remove_inline_font_styles(htm_file):
|
||||
modified_count += 1
|
||||
if modified_count % 100 == 0:
|
||||
print(f" Processed {modified_count} files...")
|
||||
|
||||
print(f"\nComplete! Modified {modified_count} files")
|
||||
print("All inline font-size styles have been removed.")
|
||||
print("The CSS rules will now control all font sizes uniformly.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
43
python/fix_navbar.py
Normal file
43
python/fix_navbar.py
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
|
||||
# The correct navbar HTML to insert
|
||||
navbar_html = '''<div class="mobile-nav">
|
||||
<div class="mobile-nav-content">
|
||||
<a href="../___left.htm" class="home-btn">🏠 Home</a>
|
||||
<div class="site-title">QuickBooks Point of Sale Help</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="offline-notice">Note: All Online Services are disabled and won't work. This is Completely Offline and No subscription needed</div>'''
|
||||
|
||||
os.chdir('/home/pts/Documents/QBPOS_Help_Web/QB_Help_Web/POS_Help')
|
||||
|
||||
# Process all htm files except special ones
|
||||
files = []
|
||||
for pattern in ['*/*.htm', '*/*/*.htm']:
|
||||
files.extend(glob.glob(pattern))
|
||||
|
||||
files = [f for f in files if not f.startswith('___')]
|
||||
|
||||
print(f"Processing {len(files)} files...")
|
||||
|
||||
for filepath in files:
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
# Remove all mobile-nav and offline-notice divs
|
||||
content = re.sub(r'<div class="mobile-nav">.*?</div>\s*</div>', '', content, flags=re.DOTALL)
|
||||
content = re.sub(r'<div class="offline-notice">.*?</div>', '', content, flags=re.DOTALL)
|
||||
|
||||
# Insert navbar after <body> tag
|
||||
content = re.sub(r'(<body[^>]*>)', r'\1\n' + navbar_html, content, count=1)
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
except Exception as e:
|
||||
print(f"Error processing {filepath}: {e}")
|
||||
|
||||
print("Done!")
|
||||
65
python/inject_style.py
Normal file
65
python/inject_style.py
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Inject inline style tag into all HTML files to force 12pt font
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def inject_style_tag(file_path):
|
||||
"""Add style tag after <head> to force 12pt."""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
original_content = content
|
||||
|
||||
# Check if our style tag already exists
|
||||
if '/* FORCE ALL FONTS TO 12PT */' in content:
|
||||
return False
|
||||
|
||||
# Inject style tag after <head> or <HEAD>
|
||||
style_injection = '''<style type="text/css">
|
||||
/* FORCE ALL FONTS TO 12PT */
|
||||
* { font-size: 12pt !important; }
|
||||
body, p, li, ol, ul, div, span, td, th, h1, h2, h3, h4, h5, h6 { font-size: 12pt !important; }
|
||||
</style>
|
||||
'''
|
||||
|
||||
# Try to inject after <head>
|
||||
content = re.sub(r'(<head[^>]*>)', r'\1\n' + style_injection, content, flags=re.IGNORECASE)
|
||||
|
||||
if content != original_content:
|
||||
with open(file_path, 'w', encoding='utf-8', errors='ignore') as f:
|
||||
f.write(content)
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error processing {file_path}: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
base_dir = Path('/home/pts/Documents/QBPOS_Help_Web/QB_Help_Web/POS_Help')
|
||||
|
||||
if not base_dir.exists():
|
||||
print(f"Error: Directory {base_dir} not found")
|
||||
return
|
||||
|
||||
# Find all .htm files
|
||||
htm_files = list(base_dir.rglob('*.htm'))
|
||||
|
||||
print(f"Found {len(htm_files)} HTML files")
|
||||
print("Injecting inline style tags...")
|
||||
|
||||
modified_count = 0
|
||||
for htm_file in htm_files:
|
||||
if inject_style_tag(htm_file):
|
||||
modified_count += 1
|
||||
if modified_count % 100 == 0:
|
||||
print(f" Processed {modified_count} files...")
|
||||
|
||||
print(f"\nComplete! Modified {modified_count} files")
|
||||
print("All files now have inline style tags forcing 12pt font.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
83
python/move_styles.py
Normal file
83
python/move_styles.py
Normal file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Move inline style injection to AFTER the qbpos.css link
|
||||
This ensures it has final say on font sizes
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def move_style_after_css_link(file_path):
|
||||
"""Move the FORCE ALL FONTS style tag to after qbpos.css link."""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
original_content = content
|
||||
|
||||
# Find the FORCE ALL FONTS style block
|
||||
style_pattern = r'<style type="text/css">\s*/\* FORCE ALL FONTS TO 12PT \*/.*?</style>\s*'
|
||||
style_match = re.search(style_pattern, content, re.DOTALL)
|
||||
|
||||
if not style_match:
|
||||
return False
|
||||
|
||||
style_block = style_match.group(0)
|
||||
|
||||
# Remove the style block from its current position
|
||||
content = content.replace(style_block, '', 1)
|
||||
|
||||
# Find the qbpos.css link and insert AFTER it
|
||||
css_link_pattern = r'(<link[^>]*qbpos\.css[^>]*>)'
|
||||
|
||||
if re.search(css_link_pattern, content):
|
||||
# Insert the style block right after the CSS link
|
||||
content = re.sub(
|
||||
css_link_pattern,
|
||||
r'\1\n' + style_block,
|
||||
content,
|
||||
count=1
|
||||
)
|
||||
else:
|
||||
# If no qbpos.css link found, put it after </head>
|
||||
content = re.sub(
|
||||
r'(</head>)',
|
||||
style_block + r'\1',
|
||||
content,
|
||||
flags=re.IGNORECASE
|
||||
)
|
||||
|
||||
if content != original_content:
|
||||
with open(file_path, 'w', encoding='utf-8', errors='ignore') as f:
|
||||
f.write(content)
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error processing {file_path}: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
base_dir = Path('/home/pts/Documents/QBPOS_Help_Web/QB_Help_Web/POS_Help')
|
||||
|
||||
if not base_dir.exists():
|
||||
print(f"Error: Directory {base_dir} not found")
|
||||
return
|
||||
|
||||
# Find all .htm files
|
||||
htm_files = list(base_dir.rglob('*.htm'))
|
||||
|
||||
print(f"Found {len(htm_files)} HTML files")
|
||||
print("Moving inline styles to after CSS links...")
|
||||
|
||||
modified_count = 0
|
||||
for htm_file in htm_files:
|
||||
if move_style_after_css_link(htm_file):
|
||||
modified_count += 1
|
||||
if modified_count % 100 == 0:
|
||||
print(f" Processed {modified_count} files...")
|
||||
|
||||
print(f"\nComplete! Modified {modified_count} files")
|
||||
print("Inline styles now come AFTER external CSS links for proper priority.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
66
python/production_server.py
Normal file
66
python/production_server.py
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Production HTTP Server for QuickBooks POS Help Documentation
|
||||
Optimized for high traffic (100,000+ users)
|
||||
"""
|
||||
import http.server
|
||||
import socketserver
|
||||
import os
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
PORT = 8888
|
||||
DIRECTORY = "QB_Help_Web"
|
||||
|
||||
# Configure minimal logging
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
class OptimizedHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
# Disable verbose request logging
|
||||
def log_message(self, format, *args):
|
||||
# Only log errors, not every request
|
||||
if args[1][0] in ('4', '5'): # 4xx and 5xx errors only
|
||||
super().log_message(format, *args)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=DIRECTORY, **kwargs)
|
||||
|
||||
def end_headers(self):
|
||||
# Disable caching to ensure changes take effect immediately
|
||||
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
self.send_header('Pragma', 'no-cache')
|
||||
self.send_header('Expires', '0')
|
||||
|
||||
super().end_headers()
|
||||
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Use ThreadingTCPServer for handling multiple simultaneous connections
|
||||
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
allow_reuse_address = True
|
||||
daemon_threads = True
|
||||
request_queue_size = 50 # Increase queue size for high traffic
|
||||
|
||||
print(f"="*60)
|
||||
print(f"QuickBooks POS Help Documentation Server")
|
||||
print(f"="*60)
|
||||
print(f"Server running at:")
|
||||
print(f" Local: http://localhost:{PORT}/POS_Help.html")
|
||||
print(f" Network: http://192.168.10.130:{PORT}/POS_Help.html")
|
||||
print(f"\nOptimizations:")
|
||||
print(f" ✓ Static asset caching (1 year)")
|
||||
print(f" ✓ Minimal logging (errors only)")
|
||||
print(f" ✓ Multi-threaded connections")
|
||||
print(f" ✓ High traffic queue (50 requests)")
|
||||
print(f"\nFor 100,000+ users, consider:")
|
||||
print(f" - Installing nginx (sudo apt install nginx)")
|
||||
print(f" - Using a CDN for static assets")
|
||||
print(f" - Load balancing across multiple servers")
|
||||
print(f"\nPress Ctrl+C to stop the server")
|
||||
print(f"="*60)
|
||||
|
||||
try:
|
||||
with ThreadedTCPServer(("0.0.0.0", PORT), OptimizedHTTPRequestHandler) as httpd:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nServer stopped.")
|
||||
135
python/rebuild_left_nav.py
Normal file
135
python/rebuild_left_nav.py
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Rebuild ___left.htm from the .hhc table of contents file
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
def parse_hhc(hhc_file):
|
||||
"""Parse the HHC file and extract navigation structure"""
|
||||
with open(hhc_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
# Extract all <object> blocks
|
||||
items = []
|
||||
level = 0
|
||||
in_ul = False
|
||||
|
||||
lines = content.split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
if '<ul>' in line.lower():
|
||||
level += 1
|
||||
in_ul = True
|
||||
elif '</ul>' in line.lower():
|
||||
level -= 1
|
||||
elif '<object type="text/sitemap">' in line.lower():
|
||||
# Get the next few lines to extract params
|
||||
obj_lines = []
|
||||
for j in range(i, min(i+10, len(lines))):
|
||||
obj_lines.append(lines[j])
|
||||
if '</object>' in lines[j].lower():
|
||||
break
|
||||
|
||||
# Parse params
|
||||
name = ''
|
||||
local = ''
|
||||
for ol in obj_lines:
|
||||
name_match = re.search(r'<param name="Name" value="([^"]+)"', ol, re.IGNORECASE)
|
||||
local_match = re.search(r'<param name="Local" value="([^"]+)"', ol, re.IGNORECASE)
|
||||
if name_match:
|
||||
name = name_match.group(1)
|
||||
if local_match:
|
||||
local = local_match.group(1)
|
||||
|
||||
if name:
|
||||
items.append({
|
||||
'level': level - 1, # Adjust for 0-based
|
||||
'name': name,
|
||||
'url': local if local else ''
|
||||
})
|
||||
|
||||
return items
|
||||
|
||||
def generate_left_htm(items):
|
||||
"""Generate the ___left.htm HTML content"""
|
||||
|
||||
html = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>Contents</title>
|
||||
<link rel="StyleSheet" href="___dtree.css" type="text/css" />
|
||||
<link rel="StyleSheet" href="prompttech-header.css" type="text/css" />
|
||||
<script language="JavaScript" src="___dtree.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body class="dtreebody">
|
||||
<div class="dtreecontainer">
|
||||
<script type="text/javascript">
|
||||
<!--
|
||||
d = new dTree('d');
|
||||
d.config.useCookies = false;
|
||||
"""
|
||||
|
||||
# Add root node
|
||||
html += "d.add(0,-1,'QuickBooks Point of Sale Help','qbpos_basic_procedures/basic_welcome.htm','QuickBooks Point of Sale Help','body');\n"
|
||||
|
||||
# Add all items
|
||||
node_id = 1
|
||||
parent_stack = [0] # Stack to track parent IDs at each level
|
||||
last_level = 0
|
||||
|
||||
for item in items:
|
||||
level = item['level']
|
||||
name = item['name'].replace("'", "\\'")
|
||||
url = item['url'].replace("'", "\\'")
|
||||
|
||||
# Adjust parent stack based on level changes
|
||||
if level > last_level:
|
||||
# Going deeper
|
||||
parent_stack.append(node_id - 1)
|
||||
elif level < last_level:
|
||||
# Going up - pop stack
|
||||
for _ in range(last_level - level):
|
||||
if len(parent_stack) > 1:
|
||||
parent_stack.pop()
|
||||
|
||||
parent_id = parent_stack[-1]
|
||||
|
||||
# Add node
|
||||
if url:
|
||||
html += f"d.add({node_id},{parent_id},'{name}','{url}');\n"
|
||||
else:
|
||||
html += f"d.add({node_id},{parent_id},'{name}');\n"
|
||||
|
||||
node_id += 1
|
||||
last_level = level
|
||||
|
||||
html += """
|
||||
document.write(d);
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
return html
|
||||
|
||||
def main():
|
||||
hhc_file = '/home/pts/Documents/QBPOS_Help_Web/QB_Help_Web/POS_Help/POS_Help_v19R1_Feb_2020.hhc'
|
||||
output_file = '/home/pts/Documents/QBPOS_Help_Web/QB_Help_Web/POS_Help/___left.htm'
|
||||
|
||||
print(f"Parsing {hhc_file}...")
|
||||
items = parse_hhc(hhc_file)
|
||||
print(f"Found {len(items)} navigation items")
|
||||
|
||||
print(f"Generating {output_file}...")
|
||||
html = generate_left_htm(items)
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
|
||||
print(f"Successfully rebuilt ___left.htm with {len(items)} items")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
204
python/secure_production_server.py
Normal file
204
python/secure_production_server.py
Normal file
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Secure Production HTTP Server for QuickBooks POS Help Documentation
|
||||
Features:
|
||||
- IP whitelist security
|
||||
- Rate limiting
|
||||
- Auto-restart capability
|
||||
- Production-ready error handling
|
||||
- Logging
|
||||
- HTTPS ready (configurable)
|
||||
"""
|
||||
import http.server
|
||||
import socketserver
|
||||
import os
|
||||
import logging
|
||||
import signal
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
from threading import Lock
|
||||
import time
|
||||
|
||||
# ============ CONFIGURATION ============
|
||||
PORT = 8888
|
||||
DIRECTORY = "QB_Help_Web"
|
||||
|
||||
# Security: Allowed IP addresses (empty list = allow all)
|
||||
# Add your allowed IPs here, e.g., ['192.168.10.0/24', '10.0.0.1']
|
||||
ALLOWED_IPS = [] # Empty = allow all. Populate for security.
|
||||
|
||||
# Rate limiting: max requests per IP per minute
|
||||
RATE_LIMIT_REQUESTS = 1000
|
||||
RATE_LIMIT_WINDOW = 60 # seconds
|
||||
|
||||
# Logging configuration
|
||||
LOG_FILE = "/tmp/qbpos_help_server.log"
|
||||
LOG_LEVEL = logging.INFO
|
||||
|
||||
# HTTPS Configuration (set to True when ready)
|
||||
USE_HTTPS = False
|
||||
SSL_CERT_FILE = "/etc/ssl/certs/server.crt" # Update path when ready
|
||||
SSL_KEY_FILE = "/etc/ssl/private/server.key" # Update path when ready
|
||||
|
||||
# ============ END CONFIGURATION ============
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=LOG_LEVEL,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(LOG_FILE),
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Rate limiting storage
|
||||
rate_limit_data = defaultdict(list)
|
||||
rate_limit_lock = Lock()
|
||||
|
||||
def is_ip_allowed(client_ip):
|
||||
"""Check if IP is in whitelist. If whitelist is empty, allow all."""
|
||||
if not ALLOWED_IPS:
|
||||
return True
|
||||
|
||||
# Simple IP matching (for production, use ipaddress module for CIDR)
|
||||
for allowed in ALLOWED_IPS:
|
||||
if client_ip.startswith(allowed.split('/')[0]):
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_rate_limit(client_ip):
|
||||
"""Check if client has exceeded rate limit."""
|
||||
with rate_limit_lock:
|
||||
current_time = time.time()
|
||||
|
||||
# Clean old entries
|
||||
rate_limit_data[client_ip] = [
|
||||
t for t in rate_limit_data[client_ip]
|
||||
if current_time - t < RATE_LIMIT_WINDOW
|
||||
]
|
||||
|
||||
# Check limit
|
||||
if len(rate_limit_data[client_ip]) >= RATE_LIMIT_REQUESTS:
|
||||
return False
|
||||
|
||||
# Add current request
|
||||
rate_limit_data[client_ip].append(current_time)
|
||||
return True
|
||||
|
||||
class SecureHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
"""Secure HTTP request handler with IP whitelist and rate limiting."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=DIRECTORY, **kwargs)
|
||||
|
||||
def do_GET(self):
|
||||
"""Handle GET requests with security checks."""
|
||||
client_ip = self.client_address[0]
|
||||
|
||||
# Check IP whitelist
|
||||
if not is_ip_allowed(client_ip):
|
||||
logger.warning(f"Blocked request from unauthorized IP: {client_ip}")
|
||||
self.send_error(403, "Forbidden: Access denied")
|
||||
return
|
||||
|
||||
# Check rate limit
|
||||
if not check_rate_limit(client_ip):
|
||||
logger.warning(f"Rate limit exceeded for IP: {client_ip}")
|
||||
self.send_error(429, "Too Many Requests: Rate limit exceeded")
|
||||
return
|
||||
|
||||
# Process request
|
||||
try:
|
||||
super().do_GET()
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing request from {client_ip}: {e}")
|
||||
self.send_error(500, "Internal Server Error")
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Log only errors and important events."""
|
||||
if args[1][0] in ('4', '5'): # 4xx and 5xx errors
|
||||
logger.warning(f"{self.client_address[0]} - {format % args}")
|
||||
|
||||
def end_headers(self):
|
||||
"""Add security and cache headers."""
|
||||
# Security headers
|
||||
self.send_header('X-Content-Type-Options', 'nosniff')
|
||||
self.send_header('X-Frame-Options', 'SAMEORIGIN')
|
||||
self.send_header('X-XSS-Protection', '1; mode=block')
|
||||
|
||||
# Cache control
|
||||
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
self.send_header('Pragma', 'no-cache')
|
||||
self.send_header('Expires', '0')
|
||||
|
||||
super().end_headers()
|
||||
|
||||
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
"""Threaded TCP server for concurrent connections."""
|
||||
allow_reuse_address = True
|
||||
daemon_threads = True
|
||||
request_queue_size = 100
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
"""Handle graceful shutdown."""
|
||||
logger.info("\nShutting down server gracefully...")
|
||||
sys.exit(0)
|
||||
|
||||
def main():
|
||||
"""Main server function."""
|
||||
# Change to script directory
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Register signal handlers
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
# Print startup information
|
||||
protocol = "HTTPS" if USE_HTTPS else "HTTP"
|
||||
print("=" * 70)
|
||||
print("QuickBooks POS Help Documentation Server - SECURE MODE")
|
||||
print("=" * 70)
|
||||
print(f"Server running at:")
|
||||
print(f" Local: {protocol.lower()}://localhost:{PORT}/POS_Help.html")
|
||||
print(f" Network: {protocol.lower()}://192.168.10.130:{PORT}/POS_Help.html")
|
||||
print(f"\nSecurity Features:")
|
||||
print(f" ✓ IP Whitelist: {'Enabled' if ALLOWED_IPS else 'Disabled (Allow All)'}")
|
||||
print(f" ✓ Rate Limiting: {RATE_LIMIT_REQUESTS} req/min per IP")
|
||||
print(f" ✓ Security Headers: Enabled")
|
||||
print(f" ✓ HTTPS: {'Enabled' if USE_HTTPS else 'Ready (Set USE_HTTPS=True)'}")
|
||||
print(f"\nLogging:")
|
||||
print(f" ✓ Log File: {LOG_FILE}")
|
||||
print(f" ✓ Log Level: {logging.getLevelName(LOG_LEVEL)}")
|
||||
print(f"\nPress Ctrl+C to stop")
|
||||
print("=" * 70)
|
||||
|
||||
logger.info(f"Server starting on port {PORT}")
|
||||
|
||||
try:
|
||||
if USE_HTTPS:
|
||||
import ssl
|
||||
# HTTPS server (when ready)
|
||||
httpd = ThreadedTCPServer(("0.0.0.0", PORT), SecureHTTPRequestHandler)
|
||||
httpd.socket = ssl.wrap_socket(
|
||||
httpd.socket,
|
||||
certfile=SSL_CERT_FILE,
|
||||
keyfile=SSL_KEY_FILE,
|
||||
server_side=True
|
||||
)
|
||||
logger.info("HTTPS enabled")
|
||||
else:
|
||||
# HTTP server
|
||||
httpd = ThreadedTCPServer(("0.0.0.0", PORT), SecureHTTPRequestHandler)
|
||||
|
||||
logger.info("Server ready to accept connections")
|
||||
httpd.serve_forever()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Server error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
39
python/server.py
Normal file
39
python/server.py
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple HTTP Server for QuickBooks POS Help Documentation
|
||||
Access via: http://localhost:8888
|
||||
"""
|
||||
import http.server
|
||||
import socketserver
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
PORT = 8888
|
||||
DIRECTORY = "QB_Help_Web"
|
||||
|
||||
class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=DIRECTORY, **kwargs)
|
||||
|
||||
def end_headers(self):
|
||||
# Disable caching for development
|
||||
self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
self.send_header('Pragma', 'no-cache')
|
||||
self.send_header('Expires', '0')
|
||||
super().end_headers()
|
||||
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Use ThreadingTCPServer for better responsiveness
|
||||
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
with ThreadedTCPServer(("0.0.0.0", PORT), MyHTTPRequestHandler) as httpd:
|
||||
print(f"Server running at:")
|
||||
print(f" Local access: http://localhost:{PORT}/POS_Help.html")
|
||||
print(f" Network access: http://192.168.10.130:{PORT}/POS_Help.html")
|
||||
print(f"\nFeatures:")
|
||||
print(f" - No caching (instant updates)")
|
||||
print(f" - Multi-threaded (faster response)")
|
||||
print(f"\nPress Ctrl+C to stop the server")
|
||||
httpd.serve_forever()
|
||||
Reference in New Issue
Block a user