Fix dark-theme colours on player delete modal and maintenance page

Both bugs had the same cause: light colours were set with inline style
attributes, which outrank the theme rules in base.html and so survived the
dark theme. Text that inherits --text-color then became light-on-light.

* manage_player.html: the delete confirmation modal used an inline
  background-color of #fefefe with the body's dark-mode text colour, making
  the message unreadable in dark mode. Rebuilt it with .modal-* classes
  driven by the base.html CSS variables (and dark-mode variants for the
  title, warning and player name).
* admin/maintenance.html: the "Automatic reset interval" panel, table
  headers and JS-rendered rows/tables hardcoded #f8f9fa/#e2e8f0/#dee2e6.
  Switched them to --bg-color / --card-bg / --border-color /
  --text-secondary and added a themed .maint-badge-pending.

Verified with computed styles in both themes; light mode is unchanged.
This commit is contained in:
2026-10-14 22:35:39 +03:00
parent 88827be7be
commit 5b69ccb02b
2 changed files with 142 additions and 44 deletions
+58 -24
View File
@@ -3,6 +3,41 @@
{% block title %}Maintenance - Admin - DigiServer v2{% endblock %} {% block title %}Maintenance - Admin - DigiServer v2{% endblock %}
{% block content %} {% block content %}
<style>
/* Theme-aware building blocks for the maintenance console. Using the
variables defined in base.html keeps every panel readable in both the
light and dark themes (inline colours would override the theme). */
.maint-panel {
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 8px;
}
.maint-thead {
background: var(--card-bg);
}
.maint-badge-pending {
display: inline-block;
padding: 3px 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
background: #fef3c7;
color: #92400e;
}
body.dark-mode .maint-badge-pending {
background: #4a3800;
color: #fbbf24;
}
/* Muted description text should stay readable on the dark card. */
body.dark-mode .card > p[style*="color: #6c757d"] {
color: var(--text-secondary) !important;
}
</style>
<div style="max-width: 1200px; margin: 0 auto;"> <div style="max-width: 1200px; margin: 0 auto;">
<div style="margin-bottom: 30px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px;"> <div style="margin-bottom: 30px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px;">
<div> <div>
@@ -64,16 +99,17 @@
</p> </p>
<form method="POST" action="{{ url_for('admin.maintenance_set_interval') }}" <form method="POST" action="{{ url_for('admin.maintenance_set_interval') }}"
style="display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin-bottom: 25px; padding: 15px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e2e8f0;"> class="maint-panel"
style="display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin-bottom: 25px; padding: 15px;">
<div> <div>
<label for="interval_days" style="display: block; font-weight: 600; margin-bottom: 6px;"> <label for="interval_days" style="display: block; font-weight: 600; margin-bottom: 6px;">
Automatic reset interval Automatic reset interval
</label> </label>
<input type="number" id="interval_days" name="interval_days" min="0" step="1" <input type="number" id="interval_days" name="interval_days" min="0" step="1"
value="{{ interval_days }}" value="{{ interval_days }}"
style="padding: 10px; border: 1px solid #ccc; border-radius: 6px; width: 140px;"> style="padding: 10px; border: 1px solid var(--border-color); border-radius: 6px; width: 140px;">
</div> </div>
<div style="flex: 1; min-width: 220px; color: #6c757d; font-size: 0.9rem;"> <div style="flex: 1; min-width: 220px; color: var(--text-secondary); font-size: 0.9rem;">
Days before a token expires automatically. Use <strong>0</strong> to disable. Days before a token expires automatically. Use <strong>0</strong> to disable.
{% if auth_config %} {% if auth_config %}
<br>Last changed by <strong>{{ auth_config.updated_by or 'unknown' }}</strong> <br>Last changed by <strong>{{ auth_config.updated_by or 'unknown' }}</strong>
@@ -95,49 +131,47 @@
<div style="max-height: 460px; overflow-y: auto;"> <div style="max-height: 460px; overflow-y: auto;">
<table class="table" style="width: 100%; border-collapse: collapse;"> <table class="table" style="width: 100%; border-collapse: collapse;">
<thead style="position: sticky; top: 0; background: #f8f9fa; z-index: 1;"> <thead class="maint-thead" style="position: sticky; top: 0; z-index: 1;">
<tr> <tr>
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Player</th> <th style="padding: 10px; text-align: left; border-bottom: 2px solid var(--border-color);">Player</th>
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Status</th> <th style="padding: 10px; text-align: left; border-bottom: 2px solid var(--border-color);">Status</th>
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Token Age</th> <th style="padding: 10px; text-align: left; border-bottom: 2px solid var(--border-color);">Token Age</th>
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Re-auth</th> <th style="padding: 10px; text-align: left; border-bottom: 2px solid var(--border-color);">Re-auth</th>
<th style="padding: 10px; text-align: right; border-bottom: 2px solid #dee2e6;">Actions</th> <th style="padding: 10px; text-align: right; border-bottom: 2px solid var(--border-color);">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for player in players %} {% for player in players %}
{% set age = player.auth_token_age_days() %} {% set age = player.auth_token_age_days() %}
<tr style="border-bottom: 1px solid #dee2e6;"> <tr style="border-bottom: 1px solid var(--border-color);">
<td style="padding: 10px;"> <td style="padding: 10px;">
<strong>{{ player.name }}</strong> <strong>{{ player.name }}</strong>
<div style="font-size: 12px; color: #6c757d;">{{ player.hostname }}</div> <div style="font-size: 12px; color: var(--text-secondary);">{{ player.hostname }}</div>
</td> </td>
<td style="padding: 10px;"> <td style="padding: 10px;">
{% if player.is_online %} {% if player.is_online %}
<span style="color: #16a34a; font-weight: 600;">● Online</span> <span style="color: #16a34a; font-weight: 600;">● Online</span>
{% else %} {% else %}
<span style="color: #94a3b8;">○ {{ player.status or 'offline' }}</span> <span style="color: var(--text-secondary);">○ {{ player.status or 'offline' }}</span>
{% endif %} {% endif %}
</td> </td>
<td style="padding: 10px;"> <td style="padding: 10px;">
{% if age is not none %} {% if age is not none %}
{{ '%.1f'|format(age) }} day(s) {{ '%.1f'|format(age) }} day(s)
{% if player.auth_token_issued_at %} {% if player.auth_token_issued_at %}
<div style="font-size: 12px; color: #6c757d;"> <div style="font-size: 12px; color: var(--text-secondary);">
issued {{ player.auth_token_issued_at | localtime('%Y-%m-%d %H:%M') }} issued {{ player.auth_token_issued_at | localtime('%Y-%m-%d %H:%M') }}
</div> </div>
{% endif %} {% endif %}
{% else %} {% else %}
<span style="color: #94a3b8;">—</span> <span style="color: var(--text-secondary);">—</span>
{% endif %} {% endif %}
</td> </td>
<td style="padding: 10px;"> <td style="padding: 10px;">
{% if player.auth_reset_required %} {% if player.auth_reset_required %}
<span style="background: #fef3c7; color: #92400e; padding: 3px 8px; border-radius: 999px; font-size: 12px; font-weight: 600;"> <span class="maint-badge-pending">pending</span>
pending
</span>
{% else %} {% else %}
<span style="color: #94a3b8; font-size: 12px;">—</span> <span style="color: var(--text-secondary); font-size: 12px;">—</span>
{% endif %} {% endif %}
</td> </td>
<td style="padding: 10px; text-align: right; white-space: nowrap;"> <td style="padding: 10px; text-align: right; white-space: nowrap;">
@@ -160,7 +194,7 @@
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="5" style="padding: 30px; text-align: center; color: #999;"> <td colspan="5" style="padding: 30px; text-align: center; color: var(--text-secondary);">
No players registered yet. No players registered yet.
</td> </td>
</tr> </tr>
@@ -200,7 +234,7 @@
function tableRow(cells) { function tableRow(cells) {
return '<tr>' + cells.map(function (c) { return '<tr>' + cells.map(function (c) {
return '<td style="padding: 8px 10px; border-bottom: 1px solid #e2e8f0;">' + c + '</td>'; return '<td style="padding: 8px 10px; border-bottom: 1px solid var(--border-color);">' + c + '</td>';
}).join('') + '</tr>'; }).join('') + '</tr>';
} }
@@ -235,7 +269,7 @@
html += banner(data.ok, html += banner(data.ok,
data.ok ? 'Database integrity OK — no problems found.' data.ok ? 'Database integrity OK — no problems found.'
: 'Issues detected — review the details below.'); : 'Issues detected — review the details below.');
html += '<div style="margin-top: 12px; font-size: 0.9rem; color: #475569;">' + html += '<div style="margin-top: 12px; font-size: 0.9rem; color: var(--text-secondary);">' +
'<div>Integrity check: <strong>' + esc(data.integrity.join(', ')) + '</strong></div>' + '<div>Integrity check: <strong>' + esc(data.integrity.join(', ')) + '</strong></div>' +
'<div>Quick check: <strong>' + esc(data.quick_check.join(', ')) + '</strong></div>' + '<div>Quick check: <strong>' + esc(data.quick_check.join(', ')) + '</strong></div>' +
'<div>Database size: <strong>' + esc(data.db_size_mb) + ' MB</strong>' + '<div>Database size: <strong>' + esc(data.db_size_mb) + ' MB</strong>' +
@@ -247,7 +281,7 @@
html += '<table class="table" style="width: 100%; border-collapse: collapse;">'; html += '<table class="table" style="width: 100%; border-collapse: collapse;">';
html += '<thead><tr>' + html += '<thead><tr>' +
['Table', 'Row ID', 'Missing parent', 'Constraint'].map(function (h) { ['Table', 'Row ID', 'Missing parent', 'Constraint'].map(function (h) {
return '<th style="padding: 8px; text-align: left; border-bottom: 2px solid #dee2e6;">' + h + '</th>'; return '<th style="padding: 8px; text-align: left; border-bottom: 2px solid var(--border-color);">' + h + '</th>';
}).join('') + '</tr></thead><tbody>'; }).join('') + '</tr></thead><tbody>';
data.foreign_key_violations.slice(0, 100).forEach(function (v) { data.foreign_key_violations.slice(0, 100).forEach(function (v) {
html += tableRow([esc(v.table), esc(v.rowid), esc(v.parent), esc(v.constraint)]); html += tableRow([esc(v.table), esc(v.rowid), esc(v.parent), esc(v.constraint)]);
@@ -299,7 +333,7 @@
html += '<table class="table" style="width: 100%; border-collapse: collapse; margin-top: 15px;">'; html += '<table class="table" style="width: 100%; border-collapse: collapse; margin-top: 15px;">';
html += '<thead><tr>' + html += '<thead><tr>' +
['Type', 'Table', 'Action', 'Count'].map(function (h) { ['Type', 'Table', 'Action', 'Count'].map(function (h) {
return '<th style="padding: 8px; text-align: left; border-bottom: 2px solid #dee2e6;">' + h + '</th>'; return '<th style="padding: 8px; text-align: left; border-bottom: 2px solid var(--border-color);">' + h + '</th>';
}).join('') + '</tr></thead><tbody>'; }).join('') + '</tr></thead><tbody>';
data.checks.forEach(function (c) { data.checks.forEach(function (c) {
if (c.error) { if (c.error) {
@@ -337,7 +371,7 @@
html += '<table class="table" style="width: 100%; border-collapse: collapse; margin-top: 15px;">'; html += '<table class="table" style="width: 100%; border-collapse: collapse; margin-top: 15px;">';
html += '<thead><tr>' + html += '<thead><tr>' +
['Record type', 'Action', 'Rows removed'].map(function (h) { ['Record type', 'Action', 'Rows removed'].map(function (h) {
return '<th style="padding: 8px; text-align: left; border-bottom: 2px solid #dee2e6;">' + h + '</th>'; return '<th style="padding: 8px; text-align: left; border-bottom: 2px solid var(--border-color);">' + h + '</th>';
}).join('') + '</tr></thead><tbody>'; }).join('') + '</tr></thead><tbody>';
data.cleaned.forEach(function (c) { data.cleaned.forEach(function (c) {
html += tableRow([esc(c.label), esc(c.action), c.rows]); html += tableRow([esc(c.label), esc(c.action), c.rows]);
+81 -17
View File
@@ -290,24 +290,24 @@
</div> </div>
<!-- Delete Confirmation Modal --> <!-- Delete Confirmation Modal -->
<div id="deleteModal" style="display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);"> <div id="deleteModal" class="modal-overlay">
<div style="background-color: #fefefe; margin: 15% auto; padding: 20px; border: 1px solid #888; border-radius: 8px; width: 90%; max-width: 500px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);"> <div class="modal-box">
<h2 style="margin-top: 0; color: #dc3545; display: flex; align-items: center; gap: 0.5rem;"> <h2 class="modal-title">
<span style="font-size: 1.5rem;">⚠️</span> <span style="font-size: 1.5rem;">⚠️</span>
Confirm Delete Confirm Delete
</h2> </h2>
<p style="font-size: 1.1rem; margin: 1.5rem 0;"> <p class="modal-text">
Are you sure you want to delete player <strong>"{{ player.name }}"</strong>? Are you sure you want to delete player <strong class="modal-player">"{{ player.name }}"</strong>?
</p> </p>
<p style="color: #dc3545; margin: 1rem 0;"> <p class="modal-warning">
<strong>Warning:</strong> This action cannot be undone. All feedback logs for this player will also be deleted. <strong>Warning:</strong> This action cannot be undone. All feedback logs for this player will also be deleted.
</p> </p>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 2rem;"> <div class="modal-actions">
<button onclick="closeDeleteModal()" class="btn" style="background: #6c757d;"> <button onclick="closeDeleteModal()" class="btn modal-cancel">
Cancel Cancel
</button> </button>
<form method="POST" action="{{ url_for('players.delete_player', player_id=player.id) }}" style="margin: 0;"> <form method="POST" action="{{ url_for('players.delete_player', player_id=player.id) }}" style="margin: 0;">
<button type="submit" class="btn" style="background: #dc3545;"> <button type="submit" class="btn btn-danger">
Yes, Delete Player Yes, Delete Player
</button> </button>
</form> </form>
@@ -316,23 +316,87 @@
</div> </div>
<style> <style>
body.dark-mode #deleteModal > div { /* Delete confirmation modal — themed via the CSS variables defined in
background-color: #1a202c; base.html so it follows the light/dark theme. Inline colours are
border-color: #4a5568; deliberately avoided: they would win over the theme rules and leave
color: #e2e8f0; the modal white (with light text) in dark mode. */
.modal-overlay {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
} }
body.dark-mode #deleteModal h2 { .modal-box {
background-color: var(--card-bg);
color: var(--text-color);
margin: 15% auto;
padding: 20px;
border: 1px solid var(--border-color);
border-radius: 8px;
width: 90%;
max-width: 500px;
box-shadow: var(--shadow-lg);
}
.modal-title {
margin-top: 0;
color: #dc3545;
display: flex;
align-items: center;
gap: 0.5rem;
}
body.dark-mode .modal-title {
color: #f87171; color: #f87171;
} }
body.dark-mode #deleteModal p { .modal-text {
color: #e2e8f0; font-size: 1.1rem;
margin: 1.5rem 0;
color: var(--text-color);
} }
body.dark-mode #deleteModal p strong { .modal-player {
color: var(--primary-color);
}
body.dark-mode .modal-player {
color: #a78bfa;
}
.modal-warning {
margin: 1rem 0;
padding: 0.75rem 1rem;
border-radius: 6px;
background: #fff3cd;
color: #856404;
}
body.dark-mode .modal-warning {
background: #4a3800;
color: #fbbf24; color: #fbbf24;
} }
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 2rem;
}
.modal-cancel {
background: #6c757d;
}
.modal-cancel:hover {
background: #5a6268;
}
</style> </style>
<script> <script>