- 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
403 lines
13 KiB
HTML
403 lines
13 KiB
HTML
{% 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 %}
|