Add edited media report page and fix image versioning

- New route GET /players/<id>/edited-media-report with tabular report
- New template edited_media_report.html with User/Filename/Date/Link columns
- Launch Report button (green) next to View All Edited Media button
- Fix: on first edit, move original file to versionized folder as original_<name>
- Fix: content lookup fallback via PlayerEdit records and path regex
- Keep Content.filename pointing to latest edit for player playlist
This commit is contained in:
2026-07-21 09:56:14 +03:00
parent 2af04e1db3
commit 05194a19b6
7 changed files with 718 additions and 54 deletions
+155 -1
View File
@@ -125,6 +125,82 @@
body.dark-mode .info-box a {
color: #90cdf4;
}
/* Deployment status */
.deploy-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.deploy-badge.pending {
background: #fff3cd;
color: #856404;
}
body.dark-mode .deploy-badge.pending {
background: #4a3800;
color: #fbbf24;
}
.deploy-badge.deployed {
background: #d4edda;
color: #155724;
}
body.dark-mode .deploy-badge.deployed {
background: #1a4d2e;
color: #86efac;
}
.deploy-badge.failed {
background: #f8d7da;
color: #721c24;
}
body.dark-mode .deploy-badge.failed {
background: #4a1a1a;
color: #fc8181;
}
.deploy-badge.deploying {
background: #cce5ff;
color: #004085;
animation: pulse-bg 1.5s ease-in-out infinite;
}
body.dark-mode .deploy-badge.deploying {
background: #1a365d;
color: #90cdf4;
}
.deploy-badge .spinner {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid rgba(0,64,133,0.3);
border-radius: 50%;
border-top-color: #004085;
animation: deploy-spin 0.8s linear infinite;
}
body.dark-mode .deploy-badge .spinner {
border-color: rgba(144,205,244,0.3);
border-top-color: #90cdf4;
}
@keyframes deploy-spin { to { transform: rotate(360deg); } }
@keyframes pulse-bg {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
.deploy-timestamp {
font-size: 11px;
color: #6c757d;
display: block;
margin-top: 2px;
}
body.dark-mode .deploy-timestamp {
color: #718096;
}
.deploy-tooltip {
cursor: help;
border-bottom: 1px dashed #aaa;
}
</style>
<div class="container">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
@@ -142,13 +218,14 @@
<th>Location</th>
<th>Orientation</th>
<th>Status</th>
<th>Deployment</th>
<th>Last Seen</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for player in players %}
<tr>
<tr id="player-row-{{ player.id }}">
<td>
<strong>{{ player.name }}</strong>
</td>
@@ -168,6 +245,9 @@
<span class="status-badge offline">Offline</span>
{% endif %}
</td>
<td id="deploy-cell-{{ player.id }}">
{% include "players/_deploy_badge.html" %}
</td>
<td>
{% if player.last_seen %}
{{ player.last_seen | localtime }}
@@ -192,4 +272,78 @@
</div>
{% endif %}
</div>
<script>
// ── Deployment status polling ────────────────────────────────────────────
(function() {
var POLL_INTERVAL = 5000; // 5 seconds
var polling = false;
// Initial check: are there any "Deploying..." badges?
var deployingBadges = document.querySelectorAll('.deploy-badge.deploying');
if (deployingBadges.length > 0) {
polling = true;
schedulePoll();
}
function schedulePoll() {
setTimeout(pollDeploymentStatus, POLL_INTERVAL);
}
function pollDeploymentStatus() {
if (!polling) return;
fetch('{{ url_for("players.deployment_status") }}')
.then(function(r) { return r.json(); })
.then(function(data) {
var anyDeploying = false;
for (var playerId in data) {
if (!data.hasOwnProperty(playerId)) continue;
var status = data[playerId];
var cell = document.getElementById('deploy-cell-' + playerId);
if (!cell) continue;
var ds = status.deployment_status;
var lds = status.last_deployment_status;
var msg = status.last_deployment_message || '';
var ts = status.last_deployment_at
? new Date(status.last_deployment_at + 'Z').toLocaleString()
: '';
if (ds === 'deployed') {
cell.innerHTML = '<span class="deploy-badge deployed" title="' + escapeHtml(msg) + '">\u2705 Deployed<span class="deploy-timestamp">' + ts + '</span></span>';
} else if (ds === 'failed') {
cell.innerHTML = '<span class="deploy-badge failed deploy-tooltip" title="' + escapeHtml(msg) + '">\u274c Failed<span class="deploy-timestamp">' + ts + '</span></span>';
} else if (ds === 'deploying') {
anyDeploying = true;
if (!cell.querySelector('.deploying')) {
cell.innerHTML = '<span class="deploy-badge deploying"><span class="spinner"></span>Deploying...</span>';
}
} else {
// No deployment or not started (pending, null, etc.)
// Don't set anyDeploying = false here — we only care about active deployments
}
}
if (anyDeploying) {
schedulePoll();
} else {
polling = false;
}
})
.catch(function() {
// Retry
if (polling) schedulePoll();
});
}
function escapeHtml(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
})();
</script>
{% endblock %}