updated app start
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user