Files
quality_app/py_app/app/templates/download_extension.html
ske087 7912885046 User management and module improvements
- Added daily_mirror module to permissions system
- Fixed user module management - updates now work correctly
- Implemented dashboard module filtering based on user permissions
- Fixed warehouse create_locations page (config parser and delete)
- Implemented POST-Redirect-GET pattern to prevent duplicate entries
- Added application license system with validation middleware
- Cleaned up debug logging code
- Improved user module selection with fetch API instead of form submit
2025-11-29 14:16:36 +02:00

403 lines
13 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block content %}
<style>
/* QZ Pairing Key Management Card */
.qz-pairing-card {
max-width: 800px;
margin: 40px auto;
padding: 32px;
background: var(--card-bg, #fff);
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
color: var(--text-color, #333);
}
.qz-pairing-card h2 {
color: var(--text-color, #333);
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 10px;
}
.qz-pairing-card h3 {
color: var(--text-color, #333);
margin-top: 32px;
margin-bottom: 16px;
}
.qz-form-group {
margin-bottom: 24px;
}
.qz-form-group label {
display: inline-block;
font-weight: 600;
color: var(--label-color, #555);
margin-right: 8px;
}
.qz-form-group input[type="text"] {
padding: 8px 12px;
border: 1px solid var(--input-border, #ddd);
border-radius: 6px;
background: var(--input-bg, #fff);
color: var(--text-color, #333);
font-size: 1em;
min-width: 250px;
}
.qz-form-group input[type="text"]:focus {
outline: none;
border-color: #2196f3;
box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);
}
.qz-form-group select {
padding: 8px 12px;
border: 1px solid var(--input-border, #ddd);
border-radius: 6px;
background: var(--input-bg, #fff);
color: var(--text-color, #333);
font-size: 1em;
min-width: 150px;
cursor: pointer;
}
.qz-form-group select:focus {
outline: none;
border-color: #2196f3;
box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);
}
.qz-form-group button {
padding: 8px 20px;
background: #4caf50;
color: white;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.qz-form-group button:hover {
background: #388e3c;
}
.btn-delete {
padding: 4px 12px;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
font-size: 0.875em;
cursor: pointer;
transition: background 0.2s;
}
.btn-delete:hover {
background: #c82333;
}
.form-row {
display: flex;
gap: 12px;
align-items: flex-end;
flex-wrap: wrap;
}
.form-field {
display: flex;
flex-direction: column;
gap: 4px;
}
.qz-result {
margin-top: 20px;
padding: 16px;
background: var(--result-bg, #e8f5e9);
border-left: 4px solid #4caf50;
border-radius: 6px;
}
.qz-result strong {
color: var(--result-label, #2e7d32);
}
.qz-result span {
font-family: 'Courier New', monospace;
background: var(--code-bg, #c8e6c9);
padding: 2px 6px;
border-radius: 3px;
color: var(--code-text, #1b5e20);
}
.qz-table {
width: 100%;
border-collapse: collapse;
margin-top: 16px;
}
.qz-table thead tr {
background: var(--table-header-bg, #f0f0f0);
}
.qz-table th {
padding: 12px;
border: 1px solid var(--table-border, #ccc);
text-align: left;
font-weight: 600;
color: var(--text-color, #333);
}
.qz-table td {
padding: 12px;
border: 1px solid var(--table-border, #ccc);
color: var(--text-color, #333);
}
.qz-table tbody tr:hover {
background: var(--table-hover, #f5f5f5);
}
.qz-table-code {
font-family: 'Courier New', monospace;
background: var(--code-bg, #f5f5f5);
padding: 4px 8px;
border-radius: 4px;
color: var(--code-text, #333);
}
/* Dark Mode Support */
body.dark-mode .qz-pairing-card {
background: #2d2d2d;
color: #e0e0e0;
--card-bg: #2d2d2d;
--text-color: #e0e0e0;
}
body.dark-mode .qz-form-group label {
color: #bbb;
--label-color: #bbb;
}
body.dark-mode .qz-form-group input[type="text"],
body.dark-mode .qz-form-group select {
background: #3a3a3a;
border-color: #555;
color: #e0e0e0;
--input-bg: #3a3a3a;
--input-border: #555;
}
body.dark-mode .qz-result {
background: #1b5e20;
--result-bg: #1b5e20;
}
body.dark-mode .qz-result strong {
color: #a5d6a7;
--result-label: #a5d6a7;
}
body.dark-mode .qz-result span {
background: #2e7d32;
color: #c8e6c9;
--code-bg: #2e7d32;
--code-text: #c8e6c9;
}
body.dark-mode .qz-table thead tr {
background: #3a3a3a;
--table-header-bg: #3a3a3a;
}
body.dark-mode .qz-table th,
body.dark-mode .qz-table td {
border-color: #555;
color: #e0e0e0;
--table-border: #555;
}
body.dark-mode .qz-table tbody tr:hover {
background: #3a3a3a;
--table-hover: #3a3a3a;
}
body.dark-mode .qz-table-code {
background: #3a3a3a;
color: #90caf9;
--code-bg: #3a3a3a;
--code-text: #90caf9;
}
</style>
<div class="qz-pairing-card">
<h2>🔐 QZ Tray Pairing Key Management</h2>
<form id="pairing-form" method="POST" action="/generate_pairing_key" class="qz-form-group">
<div class="form-row">
<div class="form-field">
<label for="printer_name">Printer Name:</label>
<input type="text" id="printer_name" name="printer_name" required placeholder="Enter printer name">
</div>
<div class="form-field">
<label for="validity_days">Validity Period:</label>
<select id="validity_days" name="validity_days">
<option value="30">30 Days</option>
<option value="60">60 Days</option>
<option value="90" selected>90 Days (Default)</option>
<option value="180">180 Days (6 Months)</option>
<option value="365">365 Days (1 Year)</option>
<option value="730">730 Days (2 Years)</option>
<option value="1825">1825 Days (5 Years)</option>
</select>
</div>
<button type="submit">🔑 Generate Pairing Key</button>
</div>
</form>
<div id="pairing-result">
{% if pairing_key %}
<div class="qz-result">
<div style="margin-bottom: 8px;">
<strong>🔑 Pairing Key:</strong> <span>{{ pairing_key }}</span>
</div>
<div style="margin-bottom: 8px;">
<strong>🖨️ Printer Name:</strong> {{ printer_name }}
</div>
<div>
<strong>⏰ Valid Until:</strong> {{ warranty_until }}
</div>
</div>
{% endif %}
</div>
<h3>📋 Active Pairing Keys</h3>
<table class="qz-table">
<thead>
<tr>
<th>🖨️ Printer Name</th>
<th>🔑 Pairing Key</th>
<th>⏰ Valid Until</th>
<th>🛠️ Actions</th>
</tr>
</thead>
<tbody>
{% for key in pairing_keys %}
<tr>
<td>{{ key.printer_name }}</td>
<td><span class="qz-table-code">{{ key.pairing_key }}</span></td>
<td>{{ key.warranty_until }}</td>
<td>
<button class="btn-delete" onclick="deletePairingKey('{{ key.pairing_key }}', '{{ key.printer_name }}')">
🗑️ Delete
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="qz-pairing-card" style="margin-top: 40px;">
<h2>🔐 Application License Management</h2>
{% if license_data %}
<div class="qz-result" style="{% if license_data.days_remaining <= 30 %}background: #fff3e0; border-left-color: #ff9800;{% elif license_data.days_remaining <= 7 %}background: #ffebee; border-left-color: #f44336;{% endif %}">
<div style="margin-bottom: 8px;">
<strong>🔑 License Key:</strong> <span>{{ license_data.license_key }}</span>
</div>
<div style="margin-bottom: 8px;">
<strong>📅 Created:</strong> {{ license_data.created_at }}
</div>
<div style="margin-bottom: 8px;">
<strong>⏰ Valid Until:</strong> {{ license_data.valid_until }}
{% if license_data.days_remaining is defined %}
{% if license_data.days_remaining > 0 %}
<span style="margin-left: 8px; padding: 2px 8px; background: {% if license_data.days_remaining <= 7 %}#f44336{% elif license_data.days_remaining <= 30 %}#ff9800{% else %}#4caf50{% endif %}; color: white; border-radius: 4px; font-size: 0.85em;">
{{ license_data.days_remaining }} days remaining
</span>
{% else %}
<span style="margin-left: 8px; padding: 2px 8px; background: #f44336; color: white; border-radius: 4px; font-size: 0.85em;">
⚠️ EXPIRED
</span>
{% endif %}
{% endif %}
</div>
<div style="margin-top: 16px;">
<form method="POST" action="/revoke_app_license" style="display: inline-block;">
<button class="btn-delete" type="submit" onclick="return confirm('Are you sure you want to REVOKE the application license?\n\nThis will prevent all non-superadmin users from accessing the application!\n\nThis action cannot be undone.');">
🗑️ Revoke License
</button>
</form>
</div>
</div>
{% else %}
<div style="padding: 24px; background: #ffebee; border-left: 4px solid #f44336; border-radius: 6px; margin-bottom: 24px;">
<strong style="color: #c62828;">⚠️ No Active License</strong>
<p style="margin: 8px 0 0 0; color: #d32f2f;">
No application license has been generated. Non-superadmin users will not be able to access the application.
</p>
</div>
{% endif %}
<h3>🔑 Generate New License</h3>
<form id="license-form" method="POST" action="/generate_app_license" class="qz-form-group">
<div class="form-row">
<div class="form-field">
<label for="license_validity_days">License Validity Period:</label>
<select id="license_validity_days" name="validity_days">
<option value="30">30 Days (1 Month)</option>
<option value="90">90 Days (3 Months)</option>
<option value="180">180 Days (6 Months)</option>
<option value="365" selected>365 Days (1 Year)</option>
<option value="730">730 Days (2 Years)</option>
<option value="1825">1825 Days (5 Years)</option>
<option value="3650">3650 Days (10 Years)</option>
</select>
</div>
<button type="submit" onclick="return confirm('{% if license_data %}This will replace the existing license key.\n\n{% endif %}Are you sure you want to generate a new application license?');">
🔑 Generate License Key
</button>
</div>
</form>
<div style="margin-top: 24px; padding: 16px; background: var(--info-bg, #e3f2fd); border-left: 4px solid #2196f3; border-radius: 6px;">
<strong style="color: var(--info-text, #0d47a1);"> License Information</strong>
<ul style="margin: 8px 0 0 0; color: var(--info-text, #1565c0); font-size: 0.9em;">
<li>The application license controls access for all non-superadmin users</li>
<li>Superadmin accounts can always access the application regardless of license status</li>
<li>When the license expires, non-superadmin users will see an expiration message</li>
<li>Generating a new license will replace any existing license</li>
</ul>
</div>
</div>
<script>
function deletePairingKey(pairingKey, printerName) {
if (confirm(`Are you sure you want to delete the pairing key for "${printerName}"?\n\nKey: ${pairingKey}\n\nThis action cannot be undone.`)) {
// Create a form and submit it
const form = document.createElement('form');
form.method = 'POST';
form.action = '/delete_pairing_key';
const keyInput = document.createElement('input');
keyInput.type = 'hidden';
keyInput.name = 'pairing_key';
keyInput.value = pairingKey;
form.appendChild(keyInput);
document.body.appendChild(form);
form.submit();
}
}
</script>
{% endblock %}