Видео проигрываеться

This commit is contained in:
2026-05-07 11:25:44 +03:00
parent 576e2b68fc
commit 6ec569f6e6
5 changed files with 251 additions and 157 deletions

View File

@@ -28,102 +28,86 @@
</div>
</div>
<!-- Tabs -->
<div class="tabs">
<button class="tab-btn active" data-tab="jobs">Задания</button>
<button class="tab-btn" data-tab="videos">Видео</button>
</div>
<!-- Tab: Задания (оригинальный интерфейс полностью сохранён) -->
<div id="tab-jobs" class="tab-content active">
<div class="action-bar">
<button class="btn btn-primary" onclick="generateJobs()">
<i class="fas fa-play"></i> Generate New Jobs
</button>
<button class="btn btn-warning" onclick="stopAllJobs()">
<i class="fas fa-stop"></i> Stop All Jobs
</button>
<button class="btn btn-danger" onclick="cleanupJobs()">
<i class="fas fa-trash-alt"></i> Cleanup Finished
</button>
<button class="btn btn-outline" onclick="refreshJobs()">
<i class="fas fa-sync-alt"></i> Refresh
</button>
<div class="status-message" id="statusMessage">
<i class="fas fa-circle" style="color: var(--accent-success); font-size: 8px;"></i>
<span>Ready</span>
</div>
</div>
<div class="stats-grid" id="statsGrid"></div>
<div class="filter-bar">
<div class="search-wrapper">
<i class="fas fa-search"></i>
<input type="text" class="search-input" id="filterInput" placeholder="Filter by filename or UID..."
onkeyup="filterTable()">
</div>
<div class="auto-refresh-badge">
<i class="fas fa-clock"></i>
<span>Auto-refresh: 60s</span>
<span id="refreshCountdown">60</span>s
</div>
</div>
<div class="table-container">
<table id="jobsTable">
<thead>
<tr>
<th data-column="filename" onclick="sortTable('filename')">
Output File <i class="fas fa-sort"></i>
</th>
<th data-column="state" onclick="sortTable('state')" class="sorted-asc">
State <i class="fas fa-sort-up"></i>
</th>
<th data-column="created" onclick="sortTable('created')">
Created <i class="fas fa-sort"></i>
</th>
<th data-column="updated" onclick="sortTable('updated')">
Updated <i class="fas fa-sort"></i>
</th>
<th data-column="uid" onclick="sortTable('uid')">
UID <i class="fas fa-sort"></i>
</th>
</tr>
</thead>
<tbody id="jobsTableBody">
<tr>
<td colspan="5">
<div class="empty-state">
<i class="fas fa-spinner fa-spin"></i>
<p>Loading jobs...</p>
</div>
</td>
</tr>
</tbody>
</table>
<!-- Action Bar -->
<div class="action-bar">
<button class="btn btn-primary" onclick="generateJobs()">
<i class="fas fa-play"></i> Generate New Jobs
</button>
<button class="btn btn-warning" onclick="stopAllJobs()">
<i class="fas fa-stop"></i> Stop All Jobs
</button>
<button class="btn btn-danger" onclick="cleanupJobs()">
<i class="fas fa-trash-alt"></i> Cleanup Finished
</button>
<button class="btn btn-outline" onclick="refreshJobs()">
<i class="fas fa-sync-alt"></i> Refresh
</button>
<div class="status-message" id="statusMessage">
<i class="fas fa-circle" style="color: var(--accent-success); font-size: 8px;"></i>
<span>Ready</span>
</div>
</div>
<!-- Tab: Видео -->
<div id="tab-videos" class="tab-content">
<div class="action-bar">
<button class="btn btn-primary" onclick="loadVideos()">
<i class="fas fa-sync-alt"></i> Обновить список
</button>
<div class="status-message" id="videosStatusMessage">
<span>Видео не загружены</span>
</div>
<!-- Stats Grid -->
<div class="stats-grid" id="statsGrid"></div>
<!-- Filter Bar -->
<div class="filter-bar">
<div class="search-wrapper">
<i class="fas fa-search"></i>
<input type="text" class="search-input" id="filterInput" placeholder="Filter by filename or UID..."
onkeyup="filterTable()">
</div>
<div class="auto-refresh-badge">
<i class="fas fa-clock"></i>
<span>Auto-refresh: 60s</span>
<span id="refreshCountdown">60</span>s
</div>
<div class="video-list" id="videoList"></div>
</div>
<!-- Video Player Modal -->
<div id="playerModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closePlayer()">&times;</span>
<video id="videoPlayer" controls style="width:100%; max-height:70vh;"></video>
</div>
<!-- Table -->
<div class="table-container">
<table id="jobsTable">
<thead>
<tr>
<th data-column="filename" onclick="sortTable('filename')">
Output File <i class="fas fa-sort"></i>
</th>
<th data-column="state" onclick="sortTable('state')" class="sorted-asc">
State <i class="fas fa-sort-up"></i>
</th>
<th data-column="created" onclick="sortTable('created')">
Created <i class="fas fa-sort"></i>
</th>
<th data-column="updated" onclick="sortTable('updated')">
Updated <i class="fas fa-sort"></i>
</th>
<th data-column="uid" onclick="sortTable('uid')">
UID <i class="fas fa-sort"></i>
</th>
<th>Preview</th>
<th>Move</th>
</tr>
</thead>
<tbody id="jobsTableBody">
<tr>
<td colspan="7">
<div class="empty-state">
<i class="fas fa-spinner fa-spin"></i>
<p>Loading jobs...</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Video Player Modal -->
<div id="playerModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closePlayer()">&times;</span>
<video id="videoPlayer" controls style="width:100%; max-height:70vh;"></video>
</div>
</div>
@@ -228,19 +212,27 @@
function renderJobs(jobs) {
const tbody = document.getElementById('jobsTableBody');
if (jobs.length === 0) {
tbody.innerHTML = '<tr><td colspan="5"><div class="empty-state"><i class="fas fa-inbox"></i><p>No jobs found</p></div></td></tr>';
tbody.innerHTML = '<tr><td colspan="7"><div class="empty-state"><i class="fas fa-inbox"></i><p>No jobs found</p></div></td></tr>';
return;
}
tbody.innerHTML = jobs.map(job => {
const stateClass = getStateClass(job.state);
const created = formatDateTime(job.created_at);
const updated = formatDateTime(job.updated_at);
const previewBtn = job.state === 'finished'
? `<button class="btn btn-outline btn-sm" onclick="playVideo('${escapeHtml(job.outfile_name)}')"><i class="fas fa-play"></i></button>`
: '';
const moveBtn = job.state === 'finished'
? `<button class="btn btn-primary btn-sm" onclick="moveVideo('${escapeHtml(job.outfile_name)}')"><i class="fas fa-share"></i></button>`
: '';
return `<tr>
<td><div class="job-filename" title="${escapeHtml(job.outfile_name)}">${escapeHtml(job.outfile_name)}</div></td>
<td><span class="badge ${stateClass}">${escapeHtml(job.state)}</span></td>
<td class="datetime">${created}</td>
<td class="datetime">${updated}</td>
<td class="uid" title="${escapeHtml(job.uid)}">${job.uid.substring(0, 10)}...</td>
<td>${previewBtn}</td>
<td>${moveBtn}</td>
</tr>`;
}).join('');
}
@@ -277,66 +269,98 @@
filteredJobs = allJobs.filter(job => job.outfile_name.toLowerCase().includes(filter) || job.uid.toLowerCase().includes(filter));
sortAndRender();
}
async function generateJobs() { /* ... тот же код ... */ }
async function stopAllJobs() { /* ... тот же код ... */ }
async function cleanupJobs() { /* ... тот же код ... */ }
function setStatus(type, message) { /* ... тот же код ... */ }
function startAutoRefresh() { /* ... тот же код ... */ }
function stopAutoRefresh() { /* ... тот же код ... */ }
function resetCountdown() { /* ... тот же код ... */ }
// Скопируйте оригинальные реализации generateJobs, stopAllJobs, cleanupJobs, setStatus, startAutoRefresh, stopAutoRefresh, resetCountdown (они у вас уже были)
// Я не стал дублировать весь код, чтобы не загромождать ответ. Просто вставьте их из вашего предыдущего файла.
// Оригинальные реализации generateJobs, stopAllJobs, cleanupJobs, setStatus, startAutoRefresh, stopAutoRefresh, resetCountdown
async function generateJobs() {
setStatus('loading', 'Generating jobs...');
try {
const response = await fetch('/api/generate', { method: 'POST' });
if (response.ok) {
setStatus('success', 'Job generation started');
setTimeout(() => refreshJobs(), 5000);
} else {
const text = await response.text();
setStatus('error', `Error: ${text}`);
}
} catch (err) {
setStatus('error', `Error: ${err}`);
}
}
// ========== НОВЫЙ КОД: Вкладки ==========
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
btn.classList.add('active');
document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
if (btn.dataset.tab === 'videos') loadVideos();
});
});
async function stopAllJobs() {
if (!confirm('Are you sure you want to stop all active jobs?')) return;
setStatus('loading', 'Stopping all jobs...');
try {
const response = await fetch('/api/jobs/stop-all', { method: 'POST' });
if (response.ok) {
setStatus('success', 'All jobs stopped');
refreshJobs();
} else {
const text = await response.text();
setStatus('error', `Error: ${text}`);
}
} catch (err) {
setStatus('error', `Error: ${err}`);
}
}
async function cleanupJobs() {
setStatus('loading', 'Cleaning up finished jobs...');
try {
const response = await fetch('/api/cleanup', { method: 'POST' });
if (response.ok) {
setStatus('success', 'Cleanup completed');
await refreshJobs();
} else {
const text = await response.text();
setStatus('error', `Error: ${text}`);
}
} catch (err) {
setStatus('error', `Error: ${err}`);
}
}
function setStatus(type, message) {
const el = document.getElementById('statusMessage');
const icons = {
loading: '<span class="spinner"></span>',
success: '<i class="fas fa-check-circle" style="color: var(--accent-success);"></i>',
error: '<i class="fas fa-times-circle" style="color: var(--accent-danger);"></i>'
};
el.innerHTML = `${icons[type] || ''} <span>${message}</span>`;
}
function startAutoRefresh() {
stopAutoRefresh();
autoRefreshTimer = setInterval(() => { refreshJobs(); resetCountdown(); }, 60000);
countdownTimer = setInterval(() => {
countdownValue--;
document.getElementById('refreshCountdown').textContent = countdownValue;
if (countdownValue <= 0) countdownValue = 60;
}, 1000);
}
function stopAutoRefresh() {
if (autoRefreshTimer) clearInterval(autoRefreshTimer);
if (countdownTimer) clearInterval(countdownTimer);
}
function resetCountdown() {
countdownValue = 60;
document.getElementById('refreshCountdown').textContent = countdownValue;
}
// ========== НОВЫЙ КОД: Видео ==========
async function loadVideos() {
try {
const response = await fetch('/api/videos');
if (!response.ok) throw new Error(await response.text());
const videos = await response.json();
renderVideoList(videos);
document.getElementById('videosStatusMessage').innerHTML = `<span>Найдено файлов: ${videos.length}</span>`;
} catch (e) {
document.getElementById('videosStatusMessage').innerHTML = `<span style="color:red;">Ошибка: ${e.message}</span>`;
}
}
function renderVideoList(videos) {
const container = document.getElementById('videoList');
if (videos.length === 0) {
container.innerHTML = '<p>Видеофайлы не найдены.</p>';
return;
}
container.innerHTML = videos.map(name => `
<div class="video-item">
<span class="video-name" title="${name}">${name}</span>
<button class="btn btn-outline btn-sm" onclick="playVideo('${name}')"><i class="fas fa-play"></i> Смотреть</button>
<button class="btn btn-primary btn-sm" onclick="moveVideo('${name}')"><i class="fas fa-share"></i> Перенести</button>
</div>
`).join('');
}
function playVideo(filename) {
const player = document.getElementById('videoPlayer');
player.src = '/api/videos/' + encodeURIComponent(filename);
document.getElementById('playerModal').style.display = 'flex';
document.getElementById('playerModal').classList.add('active');
}
function closePlayer() {
const player = document.getElementById('videoPlayer');
player.pause();
player.src = '';
document.getElementById('playerModal').style.display = 'none';
document.getElementById('playerModal').classList.remove('active');
}
async function moveVideo(filename) {
@@ -345,7 +369,7 @@
const response = await fetch('/api/videos/' + encodeURIComponent(filename) + '/move?remove=true', { method: 'POST' });
if (!response.ok) throw new Error(await response.text());
alert('Файл успешно перемещён.');
loadVideos();
refreshJobs(); // обновим список, возможно, файл исчезнет
} catch (e) {
alert('Ошибка перемещения: ' + e.message);
}