Add QR code functionality for AR template URLs

- Add QR code icon next to each tenant's AR template URL on main page
- Add modal popup to display dynamically generated QR codes
- Add routes for QR code generation (/qrcode/template and /qrcode/arinfo)
- Include Bootstrap Icons for QR code icon display
This commit is contained in:
2025-10-20 17:40:04 -04:00
parent ccac3134f3
commit 253058b45b
3 changed files with 154 additions and 10 deletions

View File

@@ -146,23 +146,64 @@ def get_ar_content_fields(tenant_id):
fields = database.get_custom_ar_fields(tenant_id)
return jsonify(fields), 200
@app.route('/<tenant:tenant_id>/arinfo', methods=['GET'])
@app.route('/<tenant:tenant_id>/arinfo', methods=['GET', 'POST'])
def get_ar_info(tenant_id):
barcode = request.args.get('barcode')
products = load_products(tenant_id)
# Get custom AR fields for this tenant
custom_fields = database.get_custom_ar_fields(tenant_id)
custom_field_names = [f['fieldName'] for f in custom_fields]
# Handle POST request - update product fields
if request.method == 'POST':
if not barcode:
return jsonify({"error": "Barcode parameter required"}), 400
if barcode not in products:
return jsonify({"error": "Product not found"}), 404
try:
# Get the updated fields from the request body
updated_fields = request.get_json()
if not isinstance(updated_fields, list):
return jsonify({"error": "Request body must be an array of fields"}), 400
# Get current product data
current_product = products[barcode]
# Update only the editable fields
for updated_field in updated_fields:
field_name = updated_field.get('fieldName')
new_value = updated_field.get('value')
# Find the field in the current product and update it
for field in current_product:
if field['fieldName'] == field_name:
# Check if the field is editable
if field.get('editable') == 'true':
field['value'] = new_value
break
# Save the updated product to database
database.save_product(barcode, tenant_id, current_product)
app.logger.info(f"Updated product {barcode} for tenant {tenant_id}")
return jsonify({"success": True}), 200
except Exception as e:
app.logger.error(f"Error updating product: {str(e)}")
return jsonify({"error": "Failed to update product"}), 500
# Helper function to convert relative image paths to absolute URLs and filter fields
def filter_and_process_fields(product_fields):
# Filter to only include fields defined in custom AR fields
filtered_fields = []
# Get field types for all custom fields
field_types = {f['fieldName']: f['fieldType'] for f in custom_fields}
for field in product_fields:
if field['fieldName'] in custom_field_names:
# Create absolute URL for IMAGE_URI fields
@@ -173,7 +214,8 @@ def get_ar_info(tenant_id):
field['value'] = f"{request.url_root.rstrip('/')}/{tenant_id}{field['value']}"
filtered_fields.append(field)
return filtered_fields
# Handle GET request - return product data
# If barcode is provided, return specific product
if barcode:
if barcode in products:
@@ -182,7 +224,7 @@ def get_ar_info(tenant_id):
response.headers['Access-Control-Allow-Origin'] = '*'
return response, 200
return jsonify({"error": "Product not found"}), 404
# Return all products if no barcode specified
# Convert all products to have absolute URLs and filter fields
all_products = {}
@@ -241,6 +283,50 @@ def serve_image(tenant_id, filename):
app.logger.warning(f"Image not found: {filename}")
return jsonify({"error": "Image not found"}), 404
@app.route('/<tenant:tenant_id>/qrcode/template', methods=['GET'])
def generate_template_qr_code(tenant_id):
"""Generate QR code for the AR Template URL"""
try:
# Get server URL from settings
server_url = database.get_server_url()
template_url = f"{server_url}/{tenant_id}/"
# Generate QR code
buffer = BytesIO()
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(template_url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save(buffer, format='PNG')
buffer.seek(0)
return Response(buffer.getvalue(), mimetype='image/png')
except Exception as e:
app.logger.error(f"QR code generation error: {str(e)}")
return jsonify({"error": "Failed to generate QR code"}), 500
@app.route('/<tenant:tenant_id>/qrcode/arinfo', methods=['GET'])
def generate_ar_qr_code(tenant_id):
"""Generate QR code for the AR API endpoint"""
try:
# Get server URL from settings
server_url = database.get_server_url()
ar_url = f"{server_url}/{tenant_id}/arinfo"
# Generate QR code
buffer = BytesIO()
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(ar_url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save(buffer, format='PNG')
buffer.seek(0)
return Response(buffer.getvalue(), mimetype='image/png')
except Exception as e:
app.logger.error(f"QR code generation error: {str(e)}")
return jsonify({"error": "Failed to generate QR code"}), 500
@app.route('/<tenant:tenant_id>/barcodes/<path:filename>', methods=['GET'])
def serve_barcode(tenant_id, filename):
# Parse filename to extract product_id and barcode type

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Select or Create Tenant - KCAP Demo Server</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
</head>
<body>
<div class="container mt-5">
@@ -38,9 +39,15 @@
<small>{{ tenant.created_at }}</small>
</div>
<p class="mb-1">Username: {{ tenant.username }}</p>
<p class="mb-1"><strong>Knox Capture AR Template URL:</strong> <code>{{ server_url }}/{{ tenant.id }}/</code></p>
<p class="mb-1">
<strong>Knox Capture AR Template URL:</strong>
<code>{{ server_url }}/{{ tenant.id }}/</code>
</p>
<small>ID: {{ tenant.id }}</small>
</a>
<button type="button" class="btn btn-sm btn-outline-primary me-2" data-bs-toggle="modal" data-bs-target="#qrModal-{{ tenant.id }}" onclick="event.stopPropagation();">
<i class="bi bi-qr-code"></i>
</button>
<form method="POST" action="/tenant/{{ tenant.id }}/delete" class="ms-3" onsubmit="return confirmDelete('{{ tenant.name }}')">
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
</form>
@@ -79,7 +86,31 @@
</div>
</div>
</div>
<!-- QR Code Modals for each tenant -->
{% for tenant in tenants %}
<div class="modal fade" id="qrModal-{{ tenant.id }}" tabindex="-1" aria-labelledby="qrModalLabel-{{ tenant.id }}" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="qrModalLabel-{{ tenant.id }}">AR Template URL QR Code - {{ tenant.name }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center">
<p class="text-muted mb-3">Scan this QR code for the Knox Capture AR Template URL:</p>
<div class="mb-3">
<img src="/{{ tenant.id }}/qrcode/template" alt="AR Template QR Code" class="img-fluid" style="max-width: 300px;">
</div>
<code class="d-block mt-2">{{ server_url }}/{{ tenant.id }}/</code>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endfor %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const reservedIds = ['admin', 'api', 'login', 'logout', 'arcontentfields', 'arinfo',

View File

@@ -90,7 +90,12 @@
<dd class="col-sm-9">{{ tenant.name }}</dd>
<dt class="col-sm-3">API Endpoint:</dt>
<dd class="col-sm-9"><code>{{ server_url }}/{{ tenant.id }}/arinfo</code></dd>
<dd class="col-sm-9">
<code>{{ server_url }}/{{ tenant.id }}/arinfo</code>
<button type="button" class="btn btn-sm btn-outline-primary ms-2" data-bs-toggle="modal" data-bs-target="#qrModal">
<i class="bi bi-qr-code"></i> QR Code
</button>
</dd>
<dt class="col-sm-3">Created:</dt>
<dd class="col-sm-9">{{ tenant.created_at }}</dd>
@@ -101,4 +106,26 @@
</div>
</div>
</div>
<!-- QR Code Modal -->
<div class="modal fade" id="qrModal" tabindex="-1" aria-labelledby="qrModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="qrModalLabel">API Endpoint QR Code</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center">
<p class="text-muted mb-3">Scan this QR code to access the AR API endpoint:</p>
<div class="mb-3">
<img src="/{{ tenant.id }}/qrcode/arinfo" alt="AR API QR Code" class="img-fluid" style="max-width: 300px;">
</div>
<code class="d-block mt-2">{{ server_url }}/{{ tenant.id }}/arinfo</code>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock %}