Files
Quality App Developer e1f3302c6b Implement boxes management module with auto-numbered box creation
- Add boxes_crates database table with BIGINT IDs and 8-digit auto-numbered box_numbers
- Implement boxes CRUD operations (add, edit, update, delete, delete_multiple)
- Create boxes route handlers with POST actions for all operations
- Add boxes.html template with 3-panel layout matching warehouse locations module
- Implement barcode generation and printing with JsBarcode and QZ Tray integration
- Add browser print fallback for when QZ Tray is not available
- Simplify create box form to single button with auto-generation
- Fix JavaScript null reference errors with proper element validation
- Convert tuple data to dictionaries for Jinja2 template compatibility
- Register boxes blueprint in Flask app initialization
2026-01-26 22:08:31 +02:00

270 lines
15 KiB
HTML

{% extends "base.html" %}
{% block title %}App Keys Management{% endblock %}
{% block content %}
<div class="container-fluid py-5">
<div class="row mb-4">
<div class="col-12">
<h1 class="mb-2">
<i class="fas fa-key"></i> App Keys Management
</h1>
<p class="text-muted mb-0">Manage API keys and printer pairing keys for QZ Tray</p>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="list-group">
<a href="{{ url_for('settings.general_settings') }}" class="list-group-item list-group-item-action">
<i class="fas fa-sliders-h"></i> General Settings
</a>
<a href="{{ url_for('settings.user_management') }}" class="list-group-item list-group-item-action">
<i class="fas fa-users"></i> User Management
</a>
<a href="{{ url_for('settings.app_keys') }}" class="list-group-item list-group-item-action active">
<i class="fas fa-key"></i> App Keys
</a>
<a href="{{ url_for('settings.database_management') }}" class="list-group-item list-group-item-action">
<i class="fas fa-cogs"></i> Database Management
</a>
<a href="{{ url_for('settings.database_settings') }}" class="list-group-item list-group-item-action">
<i class="fas fa-database"></i> Database Info
</a>
<a href="{{ url_for('settings.logs_explorer') }}" class="list-group-item list-group-item-action">
<i class="fas fa-file-alt"></i> Logs Explorer
</a>
</div>
</div>
<div class="col-md-9">
<!-- QZ Tray Pairing Keys Section -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">
<i class="fas fa-print"></i> QZ Tray Printer Pairing Keys
</h5>
</div>
<div class="card-body">
{% if error %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-circle"></i> {{ error }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
{% if success %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle"></i> {{ success }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<!-- Generate New Pairing Key Form -->
<div class="mb-4 p-3 bg-light rounded">
<h6 class="mb-3">Generate New Pairing Key</h6>
<form method="POST" action="{{ url_for('settings.generate_pairing_key') }}" class="row g-3">
<div class="col-md-6">
<label for="printer_name" class="form-label">Printer Name <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="printer_name" name="printer_name"
placeholder="e.g., Label Printer 1" required>
</div>
<div class="col-md-3">
<label for="validity_days" class="form-label">Validity (days) <span class="text-danger">*</span></label>
<select class="form-select" id="validity_days" name="validity_days" required>
<option value="30">30 Days</option>
<option value="60">60 Days</option>
<option value="90" selected>90 Days</option>
<option value="180">180 Days</option>
<option value="365">1 Year</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">&nbsp;</label>
<button type="submit" class="btn btn-success w-100">
<i class="fas fa-plus"></i> Generate Key
</button>
</div>
</form>
</div>
<!-- Active Pairing Keys Table -->
<h6 class="mb-3">Active Pairing Keys</h6>
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Printer Name</th>
<th>Pairing Key</th>
<th>Valid Until</th>
<th>Days Remaining</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% if pairing_keys %}
{% for key in pairing_keys %}
<tr>
<td>
<strong>{{ key.printer_name }}</strong>
</td>
<td>
<code class="text-primary">{{ key.pairing_key }}</code>
<button type="button" class="btn btn-sm btn-outline-secondary ms-2"
onclick="copyToClipboard('{{ key.pairing_key }}')">
<i class="fas fa-copy"></i>
</button>
</td>
<td>
<small>{{ key.valid_until }}</small>
</td>
<td>
{% set days_left = key.days_remaining %}
{% if days_left > 30 %}
<span class="badge bg-success">{{ days_left }} days</span>
{% elif days_left > 0 %}
<span class="badge bg-warning">{{ days_left }} days</span>
{% else %}
<span class="badge bg-danger">Expired</span>
{% endif %}
</td>
<td>
<form method="POST" action="{{ url_for('settings.delete_pairing_key', key_id=key.id) }}"
style="display: inline;"
onsubmit="return confirm('Are you sure you want to delete this pairing key?');">
<button type="submit" class="btn btn-sm btn-outline-danger">
<i class="fas fa-trash"></i> Delete
</button>
</form>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="5" class="text-center py-4">
<div class="empty-state-message">
<i class="fas fa-inbox" style="font-size: 24px; margin-right: 10px;"></i>
<span>No pairing keys found. Create one to enable QZ Tray printing.</span>
</div>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<!-- App API Keys Section -->
<div class="card shadow-sm">
<div class="card-header bg-light">
<h5 class="mb-0">
<i class="fas fa-code"></i> Application API Keys
</h5>
</div>
<div class="card-body">
<!-- Generate New API Key Form -->
<div class="mb-4 p-3 bg-light rounded">
<h6 class="mb-3">Generate New API Key</h6>
<form method="POST" action="{{ url_for('settings.generate_api_key') }}" class="row g-3">
<div class="col-md-6">
<label for="key_name" class="form-label">Key Name <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="key_name" name="key_name"
placeholder="e.g., External Service API" required>
</div>
<div class="col-md-3">
<label for="key_type" class="form-label">Key Type <span class="text-danger">*</span></label>
<select class="form-select" id="key_type" name="key_type" required>
<option value="app_key">App Key</option>
<option value="external_service">External Service</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">&nbsp;</label>
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-plus"></i> Generate Key
</button>
</div>
</form>
</div>
<!-- Active API Keys Table -->
<h6 class="mb-3">Active API Keys</h6>
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Key Name</th>
<th>Key Type</th>
<th>API Key</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% if api_keys %}
{% for key in api_keys %}
<tr>
<td>
<strong>{{ key.key_name }}</strong>
</td>
<td>
<span class="badge bg-info">{{ key.key_type }}</span>
</td>
<td>
<code class="text-primary">{{ key.api_key[:20] }}...</code>
<button type="button" class="btn btn-sm btn-outline-secondary ms-2"
onclick="copyToClipboard('{{ key.api_key }}')">
<i class="fas fa-copy"></i>
</button>
</td>
<td>
<small>{{ key.created_at.strftime('%Y-%m-%d %H:%M') if key.created_at else 'N/A' }}</small>
</td>
<td>
<form method="POST" action="{{ url_for('settings.delete_api_key', key_id=key.id) }}"
style="display: inline;"
onsubmit="return confirm('Are you sure you want to delete this API key?');">
<button type="submit" class="btn btn-sm btn-outline-danger">
<i class="fas fa-trash"></i> Delete
</button>
</form>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="5" class="text-center py-4">
<div class="empty-state-message">
<i class="fas fa-inbox" style="font-size: 24px; margin-right: 10px;"></i>
<span>No API keys found. Create one for external integrations.</span>
</div>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-info-circle"></i> <strong>Note:</strong> Keep your API keys secure and never share them publicly.
Regenerate keys if you suspect they have been compromised.
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
alert('Key copied to clipboard!');
}).catch(err => {
console.error('Failed to copy:', err);
alert('Failed to copy key');
});
}
</script>
{% endblock %}