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:
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user