Improve upload error handling and admin transcoding monitoring
This commit is contained in:
parent
613b962a87
commit
9c22093aca
@ -561,6 +561,7 @@
|
||||
<script>
|
||||
let adminUser = null;
|
||||
let currentAdminSection = 'overview';
|
||||
let transcodingPollInterval = null;
|
||||
|
||||
// Admin authentication
|
||||
async function checkAdminAuth() {
|
||||
@ -645,6 +646,12 @@
|
||||
}
|
||||
|
||||
async function adminLogout() {
|
||||
// Stop any polling intervals
|
||||
if (transcodingPollInterval) {
|
||||
clearInterval(transcodingPollInterval);
|
||||
transcodingPollInterval = null;
|
||||
}
|
||||
|
||||
if (window.nostrAuth) {
|
||||
await window.nostrAuth.logout();
|
||||
adminUser = null;
|
||||
@ -664,10 +671,21 @@
|
||||
document.querySelectorAll('.admin-section').forEach(sec => sec.classList.remove('active'));
|
||||
document.getElementById(section + '-section').classList.add('active');
|
||||
|
||||
// Clear existing polling intervals
|
||||
if (transcodingPollInterval) {
|
||||
clearInterval(transcodingPollInterval);
|
||||
transcodingPollInterval = null;
|
||||
}
|
||||
|
||||
// Load section data
|
||||
switch (section) {
|
||||
case 'overview': loadAdminStats(); break;
|
||||
case 'transcoding': loadTranscodingStats(); loadTranscodingJobs(); break;
|
||||
case 'transcoding':
|
||||
loadTranscodingStats();
|
||||
loadTranscodingJobs();
|
||||
// Start polling for active jobs
|
||||
startTranscodingPolling();
|
||||
break;
|
||||
case 'users': loadUsers(); break;
|
||||
case 'files': loadFiles(); break;
|
||||
case 'reports': loadReports(); break;
|
||||
@ -1202,21 +1220,56 @@
|
||||
function refreshLogs() { loadLogs(); }
|
||||
function refreshTranscodingJobs() { loadTranscodingStats(); loadTranscodingJobs(); }
|
||||
|
||||
function startTranscodingPolling() {
|
||||
// Poll every 5 seconds for active jobs
|
||||
transcodingPollInterval = setInterval(() => {
|
||||
if (currentAdminSection === 'transcoding') {
|
||||
loadTranscodingStats();
|
||||
loadTranscodingJobs();
|
||||
} else {
|
||||
// Stop polling if we're not on the transcoding section
|
||||
clearInterval(transcodingPollInterval);
|
||||
transcodingPollInterval = null;
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Transcoding Management Functions
|
||||
async function loadTranscodingStats() {
|
||||
try {
|
||||
const response = await adminFetch('/api/admin/transcoding/stats');
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 503) {
|
||||
// Transcoding not enabled
|
||||
document.getElementById('queue-length').textContent = 'N/A';
|
||||
document.getElementById('processing-jobs').textContent = 'N/A';
|
||||
document.getElementById('completed-today').textContent = 'N/A';
|
||||
document.getElementById('failed-jobs').textContent = 'N/A';
|
||||
document.getElementById('ffmpeg-status').textContent = 'Disabled';
|
||||
document.getElementById('transcode-storage').textContent = 'N/A';
|
||||
document.getElementById('avg-processing-time').textContent = 'N/A';
|
||||
document.getElementById('success-rate').textContent = 'N/A';
|
||||
|
||||
// Clear active jobs table
|
||||
const tbody = document.getElementById('active-jobs-table');
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="no-data">Transcoding service disabled</td></tr>';
|
||||
return;
|
||||
}
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Update stats cards
|
||||
document.getElementById('queue-length').textContent = data.stats.queue_length || 0;
|
||||
document.getElementById('processing-jobs').textContent = data.stats.processing_jobs || 0;
|
||||
document.getElementById('completed-today').textContent = data.stats.completed_today || 0;
|
||||
document.getElementById('failed-jobs').textContent = data.stats.failed_jobs || 0;
|
||||
document.getElementById('ffmpeg-status').textContent = data.stats.ffmpeg_status || 'Unknown';
|
||||
document.getElementById('transcode-storage').textContent = data.stats.transcoded_storage || '0 GB';
|
||||
document.getElementById('avg-processing-time').textContent = data.stats.avg_processing_time || '-- min';
|
||||
document.getElementById('success-rate').textContent = data.stats.success_rate ?
|
||||
document.getElementById('queue-length').textContent = data.stats?.queue_length || 0;
|
||||
document.getElementById('processing-jobs').textContent = data.stats?.processing_jobs || 0;
|
||||
document.getElementById('completed-today').textContent = data.stats?.completed_today || 0;
|
||||
document.getElementById('failed-jobs').textContent = data.stats?.failed_jobs || 0;
|
||||
document.getElementById('ffmpeg-status').textContent = data.stats?.ffmpeg_status || 'Unknown';
|
||||
document.getElementById('transcode-storage').textContent = data.stats?.transcoded_storage || '0 GB';
|
||||
document.getElementById('avg-processing-time').textContent = data.stats?.avg_processing_time || '-- min';
|
||||
document.getElementById('success-rate').textContent = data.stats?.success_rate ?
|
||||
`${data.stats.success_rate.toFixed(1)}%` : '--%';
|
||||
|
||||
// Update active jobs table
|
||||
@ -1224,7 +1277,10 @@
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load transcoding stats:', error);
|
||||
showToast('Failed to load transcoding stats', 'error');
|
||||
// Only show toast on first error, not during polling
|
||||
if (!transcodingPollInterval) {
|
||||
showToast('Failed to load transcoding stats: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,12 @@
|
||||
</div>
|
||||
|
||||
<div id="recent-uploads" class="recent-uploads">
|
||||
<h3>Recent Uploads</h3>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
||||
<h3 style="margin: 0;">Recent Uploads</h3>
|
||||
<button class="action-btn" onclick="gatewayUI.loadServerFiles(true)" style="font-size: 0.8rem; padding: 5px 10px;">
|
||||
↻ Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div id="uploads-list" class="uploads-list">
|
||||
<p class="empty-state">No recent uploads</p>
|
||||
</div>
|
||||
|
@ -14,6 +14,9 @@ class GatewayUI {
|
||||
|
||||
// Update service status every 30 seconds
|
||||
setInterval(() => this.checkServiceStatus(), 30000);
|
||||
|
||||
// Refresh file list every 15 seconds to pick up completed transcoding jobs
|
||||
setInterval(() => this.loadServerFiles(false), 15000);
|
||||
}
|
||||
|
||||
initializeElements() {
|
||||
@ -187,6 +190,9 @@ class GatewayUI {
|
||||
// Show progress
|
||||
this.showUploadProgress(file.name);
|
||||
|
||||
// Reset progress
|
||||
this.updateProgress(0, 0, file.size);
|
||||
|
||||
try {
|
||||
this.currentUpload = {
|
||||
file: file,
|
||||
@ -202,7 +208,7 @@ class GatewayUI {
|
||||
console.log('Upload without auth - nostrAuth:', !!window.nostrAuth, 'sessionToken:', !!window.nostrAuth?.sessionToken);
|
||||
}
|
||||
|
||||
const response = await fetch('/api/upload', {
|
||||
const response = await this.uploadWithProgress('/api/upload', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: formData,
|
||||
@ -254,12 +260,85 @@ class GatewayUI {
|
||||
this.uploadFilename.textContent = filename;
|
||||
this.uploadProgress.classList.remove('hidden');
|
||||
this.uploadArea.style.display = 'none';
|
||||
|
||||
// Start progress simulation (since we can't track real progress easily)
|
||||
this.simulateProgress();
|
||||
}
|
||||
|
||||
async uploadWithProgress(url, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
// Track upload progress
|
||||
xhr.upload.addEventListener('progress', (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const percentComplete = (e.loaded / e.total) * 100;
|
||||
this.updateProgress(percentComplete, e.loaded, e.total);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle completion
|
||||
xhr.addEventListener('load', () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
resolve(response);
|
||||
} catch (error) {
|
||||
reject(new Error('Invalid response format'));
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
|
||||
}
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
xhr.addEventListener('error', () => {
|
||||
reject(new Error('Network error occurred during upload'));
|
||||
});
|
||||
|
||||
// Handle timeout
|
||||
xhr.addEventListener('timeout', () => {
|
||||
reject(new Error('Upload timed out'));
|
||||
});
|
||||
|
||||
// Handle abort
|
||||
xhr.addEventListener('abort', () => {
|
||||
reject(new Error('AbortError'));
|
||||
});
|
||||
|
||||
// Set timeout (10 minutes for large files)
|
||||
xhr.timeout = 600000;
|
||||
|
||||
// Set headers
|
||||
Object.entries(options.headers || {}).forEach(([key, value]) => {
|
||||
xhr.setRequestHeader(key, value);
|
||||
});
|
||||
|
||||
// Handle abort signal
|
||||
if (options.signal) {
|
||||
options.signal.addEventListener('abort', () => {
|
||||
xhr.abort();
|
||||
});
|
||||
}
|
||||
|
||||
// Send request
|
||||
xhr.open(options.method || 'GET', url);
|
||||
xhr.send(options.body);
|
||||
});
|
||||
}
|
||||
|
||||
updateProgress(percent, loaded, total) {
|
||||
if (!this.currentUpload) return;
|
||||
|
||||
const elapsed = (Date.now() - this.currentUpload.startTime) / 1000;
|
||||
const speed = loaded / elapsed;
|
||||
const remaining = total > loaded ? (total - loaded) / speed : 0;
|
||||
|
||||
this.progressFill.style.width = `${percent}%`;
|
||||
this.progressPercent.textContent = `${Math.round(percent)}%`;
|
||||
this.progressSpeed.textContent = this.formatBytes(speed) + '/s';
|
||||
this.progressEta.textContent = this.formatTime(remaining);
|
||||
}
|
||||
|
||||
simulateProgress() {
|
||||
// Fallback for browsers that don't support progress events
|
||||
let progress = 0;
|
||||
const startTime = Date.now();
|
||||
|
||||
@ -316,9 +395,9 @@ class GatewayUI {
|
||||
this.loadServerFiles();
|
||||
}
|
||||
|
||||
async loadServerFiles() {
|
||||
// Show loading state
|
||||
if (this.uploadsList) {
|
||||
async loadServerFiles(showLoading = true) {
|
||||
// Show loading state only if explicitly requested
|
||||
if (this.uploadsList && showLoading) {
|
||||
this.uploadsList.innerHTML = '<div class="loading-state"><div class="spinner"></div>Loading files...</div>';
|
||||
}
|
||||
|
||||
|
@ -178,7 +178,9 @@ apt-get install -y \
|
||||
tree \
|
||||
unzip \
|
||||
wget \
|
||||
ffmpeg
|
||||
ffmpeg \
|
||||
build-essential \
|
||||
gcc
|
||||
|
||||
# Install latest stable Go version
|
||||
echo "📦 Checking for latest Go version..."
|
||||
@ -277,7 +279,8 @@ if [ "$SKIP_BUILD" = false ]; then
|
||||
# Create bin directory if it doesn't exist
|
||||
mkdir -p bin
|
||||
|
||||
# Build binary
|
||||
# Build binary with CGO enabled for SQLite support
|
||||
export CGO_ENABLED=1
|
||||
go build -o bin/gateway \
|
||||
-ldflags "-X main.version=$(git describe --tags --always 2>/dev/null || echo 'dev') -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ) -s -w" \
|
||||
./cmd/gateway
|
||||
|
@ -64,7 +64,9 @@ apt-get install -y \
|
||||
bc \
|
||||
ca-certificates \
|
||||
gnupg \
|
||||
lsb-release
|
||||
lsb-release \
|
||||
build-essential \
|
||||
gcc
|
||||
|
||||
# Create service user
|
||||
echo "👤 Creating service user..."
|
||||
@ -82,6 +84,8 @@ if [ "$SKIP_BUILD" = false ]; then
|
||||
# Create bin directory if it doesn't exist
|
||||
mkdir -p bin
|
||||
|
||||
# Build with CGO enabled for SQLite support
|
||||
export CGO_ENABLED=1
|
||||
go build -o bin/gateway \
|
||||
-ldflags "-X main.version=$(git describe --tags --always) -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ) -s -w" \
|
||||
./cmd/gateway
|
||||
@ -216,7 +220,7 @@ server {
|
||||
add_header X-Frame-Options SAMEORIGIN always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; connect-src 'self' wss: ws:;" always;
|
||||
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; img-src 'self' data: https:; connect-src 'self' wss: ws:;" always;
|
||||
|
||||
# Main application
|
||||
location / {
|
||||
|
Loading…
x
Reference in New Issue
Block a user