updated app start

This commit is contained in:
2025-08-05 16:50:46 +03:00
parent 318f783de3
commit 2e719fc029
37 changed files with 1028 additions and 198 deletions

View File

@@ -210,6 +210,59 @@
</div>
</div>
<!-- Performance Monitoring Dashboard -->
<div class="card mb-4 {{ 'dark-mode' if theme == 'dark' else '' }}">
<div class="card-header bg-success text-white">
<h2>Performance Monitor</h2>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="text-center">
<h5>CPU Usage</h5>
<div id="cpu-gauge" class="progress mb-2">
<div class="progress-bar bg-info" role="progressbar" style="width: 0%"></div>
</div>
<small id="cpu-text">0%</small>
</div>
</div>
<div class="col-md-4">
<div class="text-center">
<h5>Memory Usage</h5>
<div id="memory-gauge" class="progress mb-2">
<div class="progress-bar bg-warning" role="progressbar" style="width: 0%"></div>
</div>
<small id="memory-text">0%</small>
</div>
</div>
<div class="col-md-4">
<div class="text-center">
<h5>Disk Usage</h5>
<div id="disk-gauge" class="progress mb-2">
<div class="progress-bar bg-danger" role="progressbar" style="width: 0%"></div>
</div>
<small id="disk-text">0%</small>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<button id="toggle-monitor" class="btn btn-primary">Start Monitoring</button>
<button id="reset-stats" class="btn btn-secondary">Reset Stats</button>
<span id="monitor-status" class="ms-3 text-muted">Monitoring stopped</span>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<h6>Performance Log:</h6>
<div id="perf-log" style="height: 100px; overflow-y: scroll; background-color: #f8f9fa; padding: 10px; border-radius: 5px; font-family: monospace; font-size: 12px;">
<div class="text-muted">Performance monitoring ready...</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-4">
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Back to Dashboard</a>
</div>
@@ -227,6 +280,116 @@
popup.style.display = 'none';
}, 5000);
}
// Performance monitoring functionality
let monitoringInterval = null;
let isMonitoring = false;
let maxCpu = 0, maxMemory = 0;
function updateGauge(elementId, textId, value, color) {
const gauge = document.querySelector(`#${elementId} .progress-bar`);
const text = document.getElementById(textId);
gauge.style.width = `${value}%`;
gauge.className = `progress-bar ${color}`;
text.textContent = `${value.toFixed(1)}%`;
}
function logPerformance(message) {
const log = document.getElementById('perf-log');
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.innerHTML = `<span class="text-muted">[${timestamp}]</span> ${message}`;
log.appendChild(logEntry);
log.scrollTop = log.scrollHeight;
// Keep only last 50 entries
if (log.children.length > 50) {
log.removeChild(log.firstChild);
}
}
function updatePerformanceStats() {
fetch('/api/performance')
.then(response => response.json())
.then(data => {
if (data.error) {
logPerformance(`Error: ${data.error}`);
return;
}
// Update gauges
updateGauge('cpu-gauge', 'cpu-text', data.cpu_percent, 'bg-info');
updateGauge('memory-gauge', 'memory-text', data.memory_percent, 'bg-warning');
updateGauge('disk-gauge', 'disk-text', data.disk_percent, 'bg-danger');
// Track maximum values
if (data.cpu_percent > maxCpu) {
maxCpu = data.cpu_percent;
logPerformance(`New CPU peak: ${maxCpu.toFixed(1)}%`);
}
if (data.memory_percent > maxMemory) {
maxMemory = data.memory_percent;
logPerformance(`New Memory peak: ${maxMemory.toFixed(1)}%`);
}
// Log significant changes
if (data.cpu_percent > 80) {
logPerformance(`<span class="text-danger">High CPU usage: ${data.cpu_percent.toFixed(1)}%</span>`);
}
if (data.memory_percent > 80) {
logPerformance(`<span class="text-danger">High Memory usage: ${data.memory_percent.toFixed(1)}%</span>`);
}
})
.catch(error => {
logPerformance(`Fetch error: ${error.message}`);
});
}
function toggleMonitoring() {
const toggleButton = document.getElementById('toggle-monitor');
const statusSpan = document.getElementById('monitor-status');
if (isMonitoring) {
// Stop monitoring
clearInterval(monitoringInterval);
isMonitoring = false;
toggleButton.textContent = 'Start Monitoring';
toggleButton.className = 'btn btn-primary';
statusSpan.textContent = 'Monitoring stopped';
statusSpan.className = 'ms-3 text-muted';
logPerformance('Monitoring stopped');
} else {
// Start monitoring
isMonitoring = true;
toggleButton.textContent = 'Stop Monitoring';
toggleButton.className = 'btn btn-danger';
statusSpan.textContent = 'Monitoring active';
statusSpan.className = 'ms-3 text-success';
logPerformance('Monitoring started');
// Update immediately and then every 2 seconds
updatePerformanceStats();
monitoringInterval = setInterval(updatePerformanceStats, 2000);
}
}
function resetStats() {
maxCpu = 0;
maxMemory = 0;
const log = document.getElementById('perf-log');
log.innerHTML = '<div class="text-muted">Performance log reset...</div>';
logPerformance('Stats reset');
}
// Event listeners
document.getElementById('toggle-monitor').addEventListener('click', toggleMonitoring);
document.getElementById('reset-stats').addEventListener('click', resetStats);
// Auto-start monitoring when page loads
document.addEventListener('DOMContentLoaded', function() {
// Initial stats load
updatePerformanceStats();
});
</script>
</body>
</html>

View File

@@ -91,12 +91,31 @@
</div>
<div class="card-body">
{% if content %}
<!-- Bulk Actions Controls -->
<div class="mb-3 d-flex flex-wrap align-items-center gap-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="selectAllGroup">
<label class="form-check-label" for="selectAllGroup">
Select All
</label>
</div>
<button id="deleteSelectedGroup" class="btn btn-danger" style="display: none;">
<i class="bi bi-trash"></i> Delete Selected (<span id="selectedCountGroup">0</span>)
</button>
</div>
<ul class="list-group sortable-list" id="groupMediaList">
{% for media in content %}
<li class="list-group-item d-flex align-items-center {{ 'dark-mode' if theme == 'dark' else '' }}"
draggable="true"
data-id="{{ media.id }}"
data-position="{{ loop.index0 }}">
<!-- Checkbox for bulk selection -->
<div class="me-2">
<input type="checkbox" class="form-check-input group-media-checkbox"
value="{{ media.id }}">
</div>
<!-- Drag handle -->
<div class="drag-handle me-2" title="Drag to reorder">
<i class="bi bi-grip-vertical"></i>
@@ -219,6 +238,70 @@ document.addEventListener('DOMContentLoaded', function() {
item.dataset.position = index;
});
}
// Bulk delete functionality
const selectAllGroup = document.getElementById('selectAllGroup');
const deleteSelectedGroup = document.getElementById('deleteSelectedGroup');
const selectedCountGroup = document.getElementById('selectedCountGroup');
const groupMediaCheckboxes = document.querySelectorAll('.group-media-checkbox');
// Update selected count and toggle delete button visibility
function updateSelectedCount() {
const selectedCount = document.querySelectorAll('.group-media-checkbox:checked').length;
selectedCountGroup.textContent = selectedCount;
deleteSelectedGroup.style.display = selectedCount > 0 ? 'inline-block' : 'none';
}
// Select/Deselect all checkboxes
selectAllGroup.addEventListener('change', function() {
const isChecked = selectAllGroup.checked;
groupMediaCheckboxes.forEach(checkbox => {
checkbox.checked = isChecked;
});
updateSelectedCount();
});
// Individual checkbox change
groupMediaCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', updateSelectedCount);
});
// Delete selected button click
deleteSelectedGroup.addEventListener('click', function() {
const selectedIds = Array.from(groupMediaCheckboxes)
.filter(checkbox => checkbox.checked)
.map(checkbox => checkbox.value);
if (selectedIds.length === 0) {
alert('No media selected for deletion.');
return;
}
if (confirm(`Are you sure you want to delete ${selectedIds.length} selected media items?`)) {
// Send bulk delete request
fetch('{{ url_for("bulk_delete_group_content", group_id=group.id) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() if csrf_token else "" }}'
},
body: JSON.stringify({content_ids: selectedIds})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(`Successfully deleted ${data.deleted_count} media items.`);
location.reload(); // Reload the page to update the media list
} else {
alert('Error deleting media: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while deleting the media.');
});
}
});
});
</script>
</body>

View File

@@ -93,6 +93,19 @@
</div>
<div class="card-body">
{% if content %}
<!-- Bulk Actions Controls -->
<div class="mb-3 d-flex flex-wrap align-items-center gap-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="selectAll" {% if player.groups %}disabled{% endif %}>
<label class="form-check-label" for="selectAll">
Select All
</label>
</div>
<button id="deleteSelected" class="btn btn-danger" {% if player.groups %}disabled{% endif %} style="display: none;">
<i class="bi bi-trash"></i> Delete Selected (<span id="selectedCount">0</span>)
</button>
</div>
<ul class="list-group sortable-list" id="mediaList">
{% for media in content %}
<li class="list-group-item {% if theme == 'dark' %}dark-mode{% endif %}"
@@ -100,6 +113,13 @@
data-id="{{ media.id }}"
data-position="{{ loop.index0 }}">
<div class="d-flex flex-column flex-md-row align-items-md-center">
<!-- Checkbox for bulk selection -->
<div class="me-2">
<input type="checkbox" class="form-check-input media-checkbox"
value="{{ media.id }}"
{% if player.groups %}disabled{% endif %}>
</div>
<!-- Drag handle -->
<div class="drag-handle me-2" title="Drag to reorder">
<i class="bi bi-grip-vertical"></i>
@@ -235,6 +255,75 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
}
// Bulk delete functionality
const selectAllCheckbox = document.getElementById('selectAll');
const mediaCheckboxes = document.querySelectorAll('.media-checkbox');
const deleteSelectedButton = document.getElementById('deleteSelected');
const selectedCountSpan = document.getElementById('selectedCount');
// Update selected count and toggle delete button visibility
function updateSelectedCount() {
const selectedCount = document.querySelectorAll('.media-checkbox:checked').length;
selectedCountSpan.textContent = selectedCount;
deleteSelectedButton.style.display = selectedCount > 0 ? 'inline-block' : 'none';
}
// Select/Deselect all checkboxes
selectAllCheckbox.addEventListener('change', function() {
mediaCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
updateSelectedCount();
});
// Individual checkbox change
mediaCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
// Uncheck "Select All" if any checkbox is unchecked
if (!this.checked) {
selectAllCheckbox.checked = false;
}
updateSelectedCount();
});
});
// Delete selected media
deleteSelectedButton.addEventListener('click', function() {
const selectedIds = Array.from(mediaCheckboxes)
.filter(checkbox => checkbox.checked)
.map(checkbox => checkbox.value);
if (selectedIds.length === 0) {
alert('No media selected for deletion.');
return;
}
if (confirm(`Are you sure you want to delete ${selectedIds.length} selected media items?`)) {
// Send bulk delete request
fetch('{{ url_for("bulk_delete_player_content", player_id=player.id) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() if csrf_token else "" }}'
},
body: JSON.stringify({content_ids: selectedIds})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Selected media deleted successfully.');
location.reload(); // Reload the page to update the media list
} else {
alert('Error deleting media: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while deleting the media.');
});
}
});
});
</script>
</body>

View File

@@ -129,16 +129,44 @@
<!-- Modal for Status Updates -->
<div class="modal fade" id="statusModal" tabindex="-1" aria-labelledby="statusModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content {{ 'dark-mode' if theme == 'dark' else '' }}">
<div class="modal-header {{ 'dark-mode' if theme == 'dark' else '' }}">
<h5 class="modal-title" id="statusModalLabel">Processing Files</h5>
</div>
<div class="modal-body">
<p id="status-message">Uploading and processing your files. Please wait...</p>
<div class="progress">
<div class="progress mb-3">
<div id="progress-bar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<!-- Real-time performance monitoring during upload -->
<div class="performance-monitor mt-4">
<h6>System Load During Upload:</h6>
<div class="row">
<div class="col-4">
<small>CPU Usage</small>
<div class="progress mb-1" style="height: 20px;">
<div id="modal-cpu-bar" class="progress-bar bg-info" style="width: 0%;">0%</div>
</div>
</div>
<div class="col-4">
<small>Memory Usage</small>
<div class="progress mb-1" style="height: 20px;">
<div id="modal-memory-bar" class="progress-bar bg-warning" style="width: 0%;">0%</div>
</div>
</div>
<div class="col-4">
<small>Disk Usage</small>
<div class="progress mb-1" style="height: 20px;">
<div id="modal-disk-bar" class="progress-bar bg-danger" style="width: 0%;">0%</div>
</div>
</div>
</div>
<div class="mt-2">
<small id="perf-stats" class="text-muted">Waiting for performance data...</small>
</div>
</div>
</div>
<div class="modal-footer {{ 'dark-mode' if theme == 'dark' else '' }}">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" disabled>Close</button>
@@ -226,6 +254,9 @@
statusMessage.textContent = 'Uploading and processing your files. Please wait...';
}
// Start performance monitoring during upload
startUploadMonitoring();
// Simulate progress updates
const progressBar = document.getElementById('progress-bar');
let progress = 0;
@@ -236,6 +267,7 @@
if (progress >= 100) {
clearInterval(interval);
stopUploadMonitoring();
statusMessage.textContent = 'Files uploaded and processed successfully!';
// Enable the close button
@@ -246,6 +278,82 @@
}
}, 500);
}
// Performance monitoring during upload
let uploadMonitoringInterval = null;
let startCpu = 0, startMemory = 0;
let maxUploadCpu = 0, maxUploadMemory = 0;
function updateModalPerformance(data) {
// Update CPU bar
const cpuBar = document.getElementById('modal-cpu-bar');
cpuBar.style.width = `${data.cpu_percent}%`;
cpuBar.textContent = `${data.cpu_percent.toFixed(1)}%`;
if (data.cpu_percent > 75) cpuBar.className = 'progress-bar bg-danger';
else if (data.cpu_percent > 50) cpuBar.className = 'progress-bar bg-warning';
else cpuBar.className = 'progress-bar bg-info';
// Update Memory bar
const memoryBar = document.getElementById('modal-memory-bar');
memoryBar.style.width = `${data.memory_percent}%`;
memoryBar.textContent = `${data.memory_percent.toFixed(1)}%`;
if (data.memory_percent > 75) memoryBar.className = 'progress-bar bg-danger';
else if (data.memory_percent > 50) memoryBar.className = 'progress-bar bg-warning';
else memoryBar.className = 'progress-bar bg-warning';
// Update Disk bar
const diskBar = document.getElementById('modal-disk-bar');
diskBar.style.width = `${data.disk_percent}%`;
diskBar.textContent = `${data.disk_percent.toFixed(1)}%`;
if (data.disk_percent > 85) diskBar.className = 'progress-bar bg-danger';
else diskBar.className = 'progress-bar bg-danger';
// Track peaks
if (data.cpu_percent > maxUploadCpu) maxUploadCpu = data.cpu_percent;
if (data.memory_percent > maxUploadMemory) maxUploadMemory = data.memory_percent;
// Update stats text
const perfStats = document.getElementById('perf-stats');
const cpuChange = startCpu ? (data.cpu_percent - startCpu).toFixed(1) : '0.0';
const memChange = startMemory ? (data.memory_percent - startMemory).toFixed(1) : '0.0';
perfStats.innerHTML = `CPU: ${cpuChange > 0 ? '+' : ''}${cpuChange}% | Memory: ${memChange > 0 ? '+' : ''}${memChange}% | Peak CPU: ${maxUploadCpu.toFixed(1)}%`;
}
function startUploadMonitoring() {
// Get baseline performance
fetch('/api/performance')
.then(response => response.json())
.then(data => {
if (!data.error) {
startCpu = data.cpu_percent;
startMemory = data.memory_percent;
maxUploadCpu = data.cpu_percent;
maxUploadMemory = data.memory_percent;
updateModalPerformance(data);
}
});
// Start monitoring every 1 second during upload
uploadMonitoringInterval = setInterval(() => {
fetch('/api/performance')
.then(response => response.json())
.then(data => {
if (!data.error) {
updateModalPerformance(data);
}
})
.catch(error => {
console.log('Performance monitoring error:', error);
});
}, 1000);
}
function stopUploadMonitoring() {
if (uploadMonitoringInterval) {
clearInterval(uploadMonitoringInterval);
uploadMonitoringInterval = null;
}
}
</script>
</body>
</html>