fixing field edits and adding custom fileds
This commit is contained in:
@@ -22,29 +22,49 @@
|
||||
<div class="form-text">Must be unique. This will be used as the barcode for AR content.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Product Name *</label>
|
||||
<input type="text" class="form-control" id="name" name="name" required>
|
||||
<div class="form-text">Enter a descriptive name for the product.</div>
|
||||
</div>
|
||||
{% for field in custom_fields %}
|
||||
{% if field.fieldName != '_id' %}
|
||||
<div class="mb-3">
|
||||
<label for="field_{{ field.fieldName }}" class="form-label">{{ field.label }}</label>
|
||||
{% if field.fieldType == 'IMAGE_URI' %}
|
||||
<input type="file" class="form-control" id="image_{{ field.fieldName }}" name="image_{{ field.fieldName }}" accept="image/*" onchange="previewImage(this, 'preview_{{ field.fieldName }}')">
|
||||
<div class="form-text">Supported formats: .png, .jpg, .jpeg, .gif</div>
|
||||
<div class="mt-2">
|
||||
<img id="preview_{{ field.fieldName }}" class="image-preview" style="display: none; max-width: 200px;">
|
||||
</div>
|
||||
{% elif field.fieldType == 'TEXT' %}
|
||||
{% if field.fieldName == '_price' %}
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="text" class="form-control" id="field_{{ field.fieldName }}" name="field_{{ field.fieldName }}" pattern="[0-9]+(\.[0-9]{1,2})?" placeholder="49.99">
|
||||
</div>
|
||||
{% else %}
|
||||
<input type="text" class="form-control" id="field_{{ field.fieldName }}" name="field_{{ field.fieldName }}">
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<input type="text" class="form-control" id="field_{{ field.fieldName }}" name="field_{{ field.fieldName }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Fallback fields for backward compatibility -->
|
||||
{% if not custom_fields|selectattr('fieldName', 'equalto', '_name')|list %}
|
||||
<div class="mb-3">
|
||||
<label for="price" class="form-label">Price *</label>
|
||||
<label for="name" class="form-label">Product Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not custom_fields|selectattr('fieldName', 'equalto', '_price')|list %}
|
||||
<div class="mb-3">
|
||||
<label for="price" class="form-label">Price</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="text" class="form-control" id="price" name="price" required pattern="[0-9]+(\.[0-9]{1,2})?" placeholder="49.99">
|
||||
</div>
|
||||
<div class="form-text">Enter the price in decimal format (e.g., 49.99)</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="image" class="form-label">Product Image</label>
|
||||
<input type="file" class="form-control" id="image" name="image" accept="image/*" onchange="previewImage(this)">
|
||||
<div class="form-text">Supported formats: .png, .jpg, .jpeg, .gif</div>
|
||||
<div class="mt-2">
|
||||
<img id="image-preview" class="image-preview" style="display: none;">
|
||||
<input type="text" class="form-control" id="price" name="price" pattern="[0-9]+(\.[0-9]{1,2})?" placeholder="49.99">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{{ url_for('admin.index', tenant_id=tenant_id) }}" class="btn btn-secondary">Cancel</a>
|
||||
@@ -59,8 +79,8 @@
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
function previewImage(input) {
|
||||
var preview = document.getElementById('image-preview');
|
||||
function previewImage(input, previewId) {
|
||||
var preview = document.getElementById(previewId || 'image-preview');
|
||||
|
||||
if (input.files && input.files[0]) {
|
||||
var reader = new FileReader();
|
||||
@@ -99,4 +119,4 @@ document.getElementById('generate-id-btn').addEventListener('click', function()
|
||||
document.getElementById('product_id').value = id;
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
170
src/templates/admin/ar_fields.html
Normal file
170
src/templates/admin/ar_fields.html
Normal file
@@ -0,0 +1,170 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block title %}Manage AR Fields - KCAP Admin{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1>Manage AR Content Fields</h1>
|
||||
<p>Configure the fields that will be returned by the /arcontentfields and /arinfo endpoints.</p>
|
||||
</div>
|
||||
<a href="{{ url_for('admin.index', tenant_id=tenant_id) }}" class="btn btn-secondary">Back to Admin</a>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Current AR Fields</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if custom_fields %}
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field Name</th>
|
||||
<th>Label</th>
|
||||
<th>Type</th>
|
||||
<th>Editable</th>
|
||||
<th>Display Order</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for field in custom_fields %}
|
||||
<tr>
|
||||
<td>{{ field.fieldName }}</td>
|
||||
<td>{{ field.label }}</td>
|
||||
<td>{{ field.fieldType }}</td>
|
||||
<td>{{ field.editable }}</td>
|
||||
<td>{{ field.displayOrder }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary"
|
||||
data-id="{{ field.id }}"
|
||||
data-fieldname="{{ field.fieldName }}"
|
||||
data-label="{{ field.label }}"
|
||||
data-fieldtype="{{ field.fieldType }}"
|
||||
data-editable="{{ field.editable }}"
|
||||
data-displayorder="{{ field.displayOrder }}"
|
||||
onclick="editField(this)">Edit</button>
|
||||
<form method="POST" style="display: inline-block;">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="field_id" value="{{ field.id }}">
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to delete this field?')">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No custom fields defined. Default fields will be used.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Add/Edit AR Field</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" id="fieldForm">
|
||||
<input type="hidden" name="action" value="add" id="formAction">
|
||||
<input type="hidden" name="field_id" value="" id="fieldId">
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="fieldName">Field Name</label>
|
||||
<input type="text" class="form-control" id="fieldName" name="fieldName" required>
|
||||
<small class="form-text text-muted">Use underscore prefix for system fields (e.g., _id, _price)</small>
|
||||
<small class="form-text text-warning" id="fieldNameWarning" style="display: none;">Field name cannot be changed when editing</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="label">Label</label>
|
||||
<input type="text" class="form-control" id="label" name="label" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="fieldType">Field Type</label>
|
||||
<select class="form-control" id="fieldType" name="fieldType" required>
|
||||
{% for type in field_types %}
|
||||
<option value="{{ type }}">{{ type }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="editable">Editable</label>
|
||||
<select class="form-control" id="editable" name="editable">
|
||||
<option value="true">Yes</option>
|
||||
<option value="false">No</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="displayOrder">Display Order</label>
|
||||
<input type="number" class="form-control" id="displayOrder" name="displayOrder" value="0">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save Field</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="resetForm()">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function editField(button) {
|
||||
// Get data from button attributes
|
||||
var fieldData = {
|
||||
id: button.getAttribute('data-id'),
|
||||
fieldName: button.getAttribute('data-fieldname'),
|
||||
label: button.getAttribute('data-label'),
|
||||
fieldType: button.getAttribute('data-fieldtype'),
|
||||
editable: button.getAttribute('data-editable'),
|
||||
displayOrder: button.getAttribute('data-displayorder')
|
||||
};
|
||||
|
||||
// Populate form
|
||||
document.getElementById('formAction').value = 'update';
|
||||
document.getElementById('fieldId').value = fieldData.id;
|
||||
document.getElementById('fieldName').value = fieldData.fieldName;
|
||||
document.getElementById('label').value = fieldData.label;
|
||||
document.getElementById('fieldType').value = fieldData.fieldType;
|
||||
document.getElementById('editable').value = fieldData.editable;
|
||||
document.getElementById('displayOrder').value = fieldData.displayOrder;
|
||||
|
||||
// Disable field name when editing and show warning
|
||||
document.getElementById('fieldName').disabled = true;
|
||||
document.getElementById('fieldNameWarning').style.display = 'block';
|
||||
|
||||
// Update form heading
|
||||
var formCard = document.querySelector('.card:last-child .card-header h5');
|
||||
if (formCard) {
|
||||
formCard.textContent = 'Edit AR Field';
|
||||
}
|
||||
|
||||
// Change button text
|
||||
document.querySelector('button[type="submit"]').textContent = 'Update Field';
|
||||
|
||||
// Scroll to form
|
||||
document.getElementById('fieldForm').scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
document.getElementById('formAction').value = 'add';
|
||||
document.getElementById('fieldId').value = '';
|
||||
document.getElementById('fieldForm').reset();
|
||||
|
||||
// Enable field name and hide warning
|
||||
document.getElementById('fieldName').disabled = false;
|
||||
document.getElementById('fieldNameWarning').style.display = 'none';
|
||||
|
||||
// Reset form heading
|
||||
var formCard = document.querySelector('.card:last-child .card-header h5');
|
||||
if (formCard) {
|
||||
formCard.textContent = 'Add/Edit AR Field';
|
||||
}
|
||||
|
||||
// Reset button text
|
||||
document.querySelector('button[type="submit"]').textContent = 'Save Field';
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -17,52 +17,80 @@
|
||||
<div class="form-text">Product ID cannot be changed.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Product Name *</label>
|
||||
{% for field in custom_fields %}
|
||||
{% if field.fieldName != '_id' %}
|
||||
<div class="mb-3">
|
||||
<label for="field_{{ field.fieldName }}" class="form-label">{{ field.label }}</label>
|
||||
|
||||
{% set current_value = '' %}
|
||||
{% for prod_field in product %}
|
||||
{% if prod_field.fieldName == field.fieldName %}
|
||||
{% set current_value = prod_field.value %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if field.fieldType == 'IMAGE_URI' %}
|
||||
<input type="file" class="form-control" id="image_{{ field.fieldName }}" name="image_{{ field.fieldName }}" accept="image/*" onchange="previewImage(this, 'preview_new_{{ field.fieldName }}')">
|
||||
<div class="form-text">Leave empty to keep the current image.</div>
|
||||
|
||||
{% if current_value %}
|
||||
<div class="mt-3">
|
||||
<label class="form-label">Current Image:</label>
|
||||
<div>
|
||||
<img src="{{ current_value }}" alt="Current image" class="image-preview" style="max-width: 200px;">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-2">
|
||||
<label class="form-label">New Image Preview:</label>
|
||||
<img id="preview_new_{{ field.fieldName }}" class="image-preview" style="display: none; max-width: 200px;">
|
||||
</div>
|
||||
{% elif field.fieldType == 'TEXT' %}
|
||||
{% if field.fieldName == '_price' %}
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
{% set price_value = current_value|replace('$', '') %}
|
||||
<input type="text" class="form-control" id="field_{{ field.fieldName }}" name="field_{{ field.fieldName }}"
|
||||
pattern="[0-9]+(\.[0-9]{1,2})?" value="{{ price_value }}" placeholder="49.99">
|
||||
</div>
|
||||
{% else %}
|
||||
<input type="text" class="form-control" id="field_{{ field.fieldName }}" name="field_{{ field.fieldName }}" value="{{ current_value }}">
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<input type="text" class="form-control" id="field_{{ field.fieldName }}" name="field_{{ field.fieldName }}" value="{{ current_value }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Fallback fields for backward compatibility -->
|
||||
{% if not custom_fields|selectattr('fieldName', 'equalto', '_name')|list %}
|
||||
{% for field in product %}
|
||||
{% if field.fieldName == '_name' %}
|
||||
<input type="text" class="form-control" id="name" name="name" required value="{{ field.value }}">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Product Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="{{ field.value }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="form-text">Enter a descriptive name for the product.</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="price" class="form-label">Price *</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
{% for field in product %}
|
||||
{% if field.fieldName == '_price' %}
|
||||
{% if not custom_fields|selectattr('fieldName', 'equalto', '_price')|list %}
|
||||
{% for field in product %}
|
||||
{% if field.fieldName == '_price' %}
|
||||
<div class="mb-3">
|
||||
<label for="price" class="form-label">Price</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
{% set price_value = field.value|replace('$', '') %}
|
||||
<input type="text" class="form-control" id="price" name="price" required
|
||||
pattern="[0-9]+(\.[0-9]{1,2})?" value="{{ price_value }}">
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="form-text">Enter the price in decimal format (e.g., 49.99)</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="image" class="form-label">Product Image</label>
|
||||
<input type="file" class="form-control" id="image" name="image" accept="image/*" onchange="previewImage(this)">
|
||||
<div class="form-text">Leave empty to keep the current image.</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<label class="form-label">Current Image:</label>
|
||||
{% for field in product %}
|
||||
{% if field.fieldName == '_image' %}
|
||||
<div>
|
||||
<img src="{{ field.value }}" alt="Current product image" class="image-preview">
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<label class="form-label">New Image Preview:</label>
|
||||
<img id="image-preview" class="image-preview" style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="price" name="price"
|
||||
pattern="[0-9]+(\.[0-9]{1,2})?" value="{{ price_value }}" placeholder="49.99">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{{ url_for('admin.index', tenant_id=tenant_id) }}" class="btn btn-secondary">Cancel</a>
|
||||
@@ -77,8 +105,8 @@
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
function previewImage(input) {
|
||||
var preview = document.getElementById('image-preview');
|
||||
function previewImage(input, previewId) {
|
||||
var preview = document.getElementById(previewId || 'image-preview');
|
||||
|
||||
if (input.files && input.files[0]) {
|
||||
var reader = new FileReader();
|
||||
@@ -94,4 +122,4 @@ function previewImage(input) {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -9,6 +9,7 @@
|
||||
<strong>Current Tenant:</strong> {{ tenant.name }} (ID: {{ tenant.id }})
|
||||
<div class="mt-2">
|
||||
<a href="{{ url_for('admin.manage_credentials', tenant_id=tenant.id) }}" class="btn btn-sm btn-secondary">Manage Credentials</a>
|
||||
<a href="{{ url_for('admin.manage_ar_fields', tenant_id=tenant.id) }}" class="btn btn-sm btn-secondary">Manage AR Fields</a>
|
||||
<a href="/" class="btn btn-sm btn-outline-secondary">Switch Tenant</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,36 +7,45 @@
|
||||
<div class="col-md-12">
|
||||
<div class="jumbotron">
|
||||
<h1 class="display-4">KCAP Demo Server{% if tenant %} - {{ tenant.name }}{% endif %}</h1>
|
||||
<p class="lead">A simple Flask API for demonstrating AR content retrieval for barcode scanning applications.</p>
|
||||
<p class="lead">Manage your product catalog and AR content for barcode scanning applications.</p>
|
||||
<hr class="my-4">
|
||||
<p>This server simulates the Knox Capture API for AR overlays and includes endpoints for managing product attributes.</p>
|
||||
|
||||
<div class="mt-4">
|
||||
<h2>API Endpoints</h2>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<strong>Login:</strong> <code>GET {% if tenant %}/{{ tenant.id }}{% endif %}/login</code>
|
||||
<p>Authenticates using Basic Auth with tenant-specific credentials.</p>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Content Fields:</strong> <code>GET {% if tenant %}/{{ tenant.id }}{% endif %}/arcontentfields</code>
|
||||
<p>Returns a list of available attributes (e.g., item ID, price, image URI).</p>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>AR Info:</strong> <code>GET {% if tenant %}/{{ tenant.id }}{% endif %}/arinfo?barcode=123456</code>
|
||||
<p>Returns product details for a given barcode, including image URLs.</p>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Static Image Server:</strong> <code>GET {% if tenant %}/{{ tenant.id }}{% endif %}/images/123456.png</code>
|
||||
<p>Serves product images stored in the database.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Quick Actions</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Product Management</h5>
|
||||
<p class="card-text">Add, edit, and manage your product catalog with custom fields.</p>
|
||||
{% if tenant %}
|
||||
<a href="/{{ tenant.id }}/admin/" class="btn btn-primary">Manage Products</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">AR Field Configuration</h5>
|
||||
<p class="card-text">Customize the fields returned by AR content endpoints.</p>
|
||||
{% if tenant %}
|
||||
<a href="/{{ tenant.id }}/admin/ar_fields" class="btn btn-primary">Configure AR Fields</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
{% if tenant %}
|
||||
<a href="/{{ tenant.id }}/admin/" class="btn btn-primary btn-lg">Go to Admin Interface</a>
|
||||
{% else %}
|
||||
<a href="/admin" class="btn btn-primary btn-lg">Go to Admin Interface</a>
|
||||
{% endif %}
|
||||
<h2>Getting Started</h2>
|
||||
<ol>
|
||||
<li>Configure your AR fields to define what information is returned for products</li>
|
||||
<li>Add products to your catalog with the configured fields</li>
|
||||
<li>Generate barcodes for your products</li>
|
||||
<li>Use the AR endpoints in your scanning application</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user