adding server settings

This commit is contained in:
2025-06-19 16:03:50 -04:00
parent 000c054734
commit e744afbe26
4 changed files with 140 additions and 7 deletions

View File

@@ -28,7 +28,8 @@ def load_products(tenant_id=None):
def index():
# Show a tenant selection page or redirect to default
tenants = database.get_all_tenants()
return render_template('tenant_selection.html', tenants=tenants)
server_url = database.get_server_url()
return render_template('tenant_selection.html', tenants=tenants, server_url=server_url)
@app.route('/tenant/<tenant:tenant_id>/delete', methods=['POST'])
def delete_tenant(tenant_id):
@@ -37,6 +38,22 @@ def delete_tenant(tenant_id):
flash(f'Tenant "{tenant_id}" has been deleted successfully.', 'success')
return redirect('/')
@app.route('/settings', methods=['GET', 'POST'])
def settings():
if request.method == 'POST':
server_url = request.form.get('server_url', '').strip()
if server_url:
# Remove trailing slash for consistency
server_url = server_url.rstrip('/')
database.set_setting('server_url', server_url)
flash('Server settings updated successfully.', 'success')
else:
flash('Please provide a valid server URL.', 'error')
return redirect('/settings')
server_url = database.get_server_url()
return render_template('settings.html', server_url=server_url)
@app.route('/<tenant:tenant_id>/')
def tenant_index(tenant_id):
# Auto-create tenant if it doesn't exist
@@ -56,8 +73,8 @@ def check_basic_auth(auth_header, tenant_id):
credentials = base64.b64decode(auth_header[6:]).decode('utf-8')
username, password = credentials.split(':', 1)
# Get tenant credentials from database
tenant = database.get_or_create_tenant(tenant_id)
# Get tenant credentials from database (without creating)
tenant = database.get_tenant(tenant_id)
if tenant and username == tenant['username'] and password == tenant['password']:
return True
except Exception:

View File

@@ -107,6 +107,16 @@ def init_database():
)
''')
# Create settings table for server configuration
cursor.execute('''
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
def migrate_from_json():
@@ -342,10 +352,34 @@ def get_product_image(product_id: str, tenant_id: str) -> Optional[tuple[bytes,
return row['image_data'], row['image_mime_type']
return None
def get_tenant(tenant_id: str) -> Optional[Dict[str, Any]]:
"""Get a tenant without creating it"""
# Normalize tenant_id to lowercase
tenant_id = tenant_id.lower()
with get_db() as conn:
cursor = conn.cursor()
# Check if tenant exists
cursor.execute('SELECT id, name, username, password FROM tenants WHERE id = ?', (tenant_id,))
row = cursor.fetchone()
if row:
return {
'id': row['id'],
'name': row['name'],
'username': row['username'],
'password': row['password']
}
return None
def get_or_create_tenant(tenant_id: str, username: str = None, password: str = None) -> Optional[Dict[str, Any]]:
"""Get or create a tenant"""
# Normalize tenant_id to lowercase
tenant_id = tenant_id.lower()
# Check if tenant_id is reserved
if tenant_id.lower() in RESERVED_TENANT_IDS:
if tenant_id in RESERVED_TENANT_IDS:
return None
with get_db() as conn:
@@ -366,10 +400,12 @@ def get_or_create_tenant(tenant_id: str, username: str = None, password: str = N
# Create new tenant with default credentials
default_username = username or 'admin'
default_password = password or 'admin'
# Preserve original casing for display name
display_name = tenant_id.replace('-', ' ').replace('_', ' ').title()
cursor.execute('''
INSERT INTO tenants (id, name, username, password)
VALUES (?, ?, ?, ?)
''', (tenant_id, tenant_id.title(), default_username, default_password))
''', (tenant_id, display_name, default_username, default_password))
conn.commit()
# Initialize default AR fields for the new tenant
@@ -377,7 +413,7 @@ def get_or_create_tenant(tenant_id: str, username: str = None, password: str = N
return {
'id': tenant_id,
'name': tenant_id.title(),
'name': display_name,
'username': default_username,
'password': default_password
}
@@ -554,4 +590,30 @@ def get_product_image_by_field(product_id: str, tenant_id: str, field_name: str)
row = cursor.fetchone()
if row and row['image_data']:
return row['image_data'], row['image_mime_type']
return None
return None
def get_setting(key: str, default_value: str = None) -> Optional[str]:
"""Get a setting value by key"""
with get_db() as conn:
cursor = conn.cursor()
cursor.execute('SELECT value FROM settings WHERE key = ?', (key,))
row = cursor.fetchone()
if row:
return row['value']
return default_value
def set_setting(key: str, value: str):
"""Set a setting value"""
with get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO settings (key, value)
VALUES (?, ?)
ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = CURRENT_TIMESTAMP
''', (key, value, value))
conn.commit()
def get_server_url() -> str:
"""Get the configured server URL or return a default"""
return get_setting('server_url', 'http://localhost:5000')

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Settings - KCAP Demo Server</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1 class="text-center mb-4">Server Settings</h1>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ 'success' if category == 'success' else 'danger' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3>Server Configuration</h3>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="server_url" class="form-label">Server URL</label>
<input type="url" class="form-control" id="server_url" name="server_url"
value="{{ server_url }}" placeholder="http://localhost:5000" required>
<small class="form-text text-muted">
This URL will be used for generating Knox Capture AR Template URLs.
Include the protocol (http:// or https://) and port if needed.
</small>
</div>
<button type="submit" class="btn btn-primary">Save Settings</button>
<a href="/" class="btn btn-secondary">Back to Home</a>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -38,6 +38,7 @@
<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>
<small>ID: {{ tenant.id }}</small>
</a>
<form method="POST" action="/tenant/{{ tenant.id }}/delete" class="ms-3" onsubmit="return confirmDelete('{{ tenant.name }}')">
@@ -73,6 +74,7 @@
<strong>Note:</strong> When you access a tenant URL directly (e.g., /my-tenant/),
it will be automatically created with default credentials (admin/admin).
</p>
<a href="/settings" class="btn btn-secondary mt-3">Server Settings</a>
</div>
</div>
</div>