adding server settings
This commit is contained in:
23
src/app.py
23
src/app.py
@@ -28,7 +28,8 @@ def load_products(tenant_id=None):
|
|||||||
def index():
|
def index():
|
||||||
# Show a tenant selection page or redirect to default
|
# Show a tenant selection page or redirect to default
|
||||||
tenants = database.get_all_tenants()
|
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'])
|
@app.route('/tenant/<tenant:tenant_id>/delete', methods=['POST'])
|
||||||
def delete_tenant(tenant_id):
|
def delete_tenant(tenant_id):
|
||||||
@@ -37,6 +38,22 @@ def delete_tenant(tenant_id):
|
|||||||
flash(f'Tenant "{tenant_id}" has been deleted successfully.', 'success')
|
flash(f'Tenant "{tenant_id}" has been deleted successfully.', 'success')
|
||||||
return redirect('/')
|
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>/')
|
@app.route('/<tenant:tenant_id>/')
|
||||||
def tenant_index(tenant_id):
|
def tenant_index(tenant_id):
|
||||||
# Auto-create tenant if it doesn't exist
|
# 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')
|
credentials = base64.b64decode(auth_header[6:]).decode('utf-8')
|
||||||
username, password = credentials.split(':', 1)
|
username, password = credentials.split(':', 1)
|
||||||
|
|
||||||
# Get tenant credentials from database
|
# Get tenant credentials from database (without creating)
|
||||||
tenant = database.get_or_create_tenant(tenant_id)
|
tenant = database.get_tenant(tenant_id)
|
||||||
if tenant and username == tenant['username'] and password == tenant['password']:
|
if tenant and username == tenant['username'] and password == tenant['password']:
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -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()
|
conn.commit()
|
||||||
|
|
||||||
def migrate_from_json():
|
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 row['image_data'], row['image_mime_type']
|
||||||
return None
|
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]]:
|
def get_or_create_tenant(tenant_id: str, username: str = None, password: str = None) -> Optional[Dict[str, Any]]:
|
||||||
"""Get or create a tenant"""
|
"""Get or create a tenant"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
# Check if tenant_id is reserved
|
# Check if tenant_id is reserved
|
||||||
if tenant_id.lower() in RESERVED_TENANT_IDS:
|
if tenant_id in RESERVED_TENANT_IDS:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
with get_db() as conn:
|
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
|
# Create new tenant with default credentials
|
||||||
default_username = username or 'admin'
|
default_username = username or 'admin'
|
||||||
default_password = password or 'admin'
|
default_password = password or 'admin'
|
||||||
|
# Preserve original casing for display name
|
||||||
|
display_name = tenant_id.replace('-', ' ').replace('_', ' ').title()
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO tenants (id, name, username, password)
|
INSERT INTO tenants (id, name, username, password)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
''', (tenant_id, tenant_id.title(), default_username, default_password))
|
''', (tenant_id, display_name, default_username, default_password))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Initialize default AR fields for the new tenant
|
# 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 {
|
return {
|
||||||
'id': tenant_id,
|
'id': tenant_id,
|
||||||
'name': tenant_id.title(),
|
'name': display_name,
|
||||||
'username': default_username,
|
'username': default_username,
|
||||||
'password': default_password
|
'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()
|
row = cursor.fetchone()
|
||||||
if row and row['image_data']:
|
if row and row['image_data']:
|
||||||
return row['image_data'], row['image_mime_type']
|
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')
|
||||||
52
src/templates/settings.html
Normal file
52
src/templates/settings.html
Normal 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>
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
<small>{{ tenant.created_at }}</small>
|
<small>{{ tenant.created_at }}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1">Username: {{ tenant.username }}</p>
|
<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>
|
<small>ID: {{ tenant.id }}</small>
|
||||||
</a>
|
</a>
|
||||||
<form method="POST" action="/tenant/{{ tenant.id }}/delete" class="ms-3" onsubmit="return confirmDelete('{{ tenant.name }}')">
|
<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/),
|
<strong>Note:</strong> When you access a tenant URL directly (e.g., /my-tenant/),
|
||||||
it will be automatically created with default credentials (admin/admin).
|
it will be automatically created with default credentials (admin/admin).
|
||||||
</p>
|
</p>
|
||||||
|
<a href="/settings" class="btn btn-secondary mt-3">Server Settings</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user