Project data management phase 1. Files can be downloaded to server and downloaded locally.
This commit is contained in:
@@ -1,126 +0,0 @@
|
||||
<!-- Data Files List -->
|
||||
{% if files %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
||||
<tr>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
File Name
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Type
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Size
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Created
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Session
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-slate-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{% for item in files %}
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors">
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ item.file.file_path.split('/')[-1] if item.file.file_path else 'Unknown' }}
|
||||
</div>
|
||||
{% if item.file.file_path %}
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 font-mono">
|
||||
{{ item.file.file_path }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<span class="px-2 py-1 text-xs font-medium rounded-full
|
||||
{% if item.file.file_type == 'audio' %}bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300
|
||||
{% elif item.file.file_type == 'data' %}bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300
|
||||
{% elif item.file.file_type == 'log' %}bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300
|
||||
{% else %}bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300{% endif %}">
|
||||
{{ item.file.file_type or 'unknown' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900 dark:text-white">
|
||||
{% if item.file.file_size_bytes %}
|
||||
{% if item.file.file_size_bytes < 1024 %}
|
||||
{{ item.file.file_size_bytes }} B
|
||||
{% elif item.file.file_size_bytes < 1048576 %}
|
||||
{{ "%.1f"|format(item.file.file_size_bytes / 1024) }} KB
|
||||
{% elif item.file.file_size_bytes < 1073741824 %}
|
||||
{{ "%.1f"|format(item.file.file_size_bytes / 1048576) }} MB
|
||||
{% else %}
|
||||
{{ "%.2f"|format(item.file.file_size_bytes / 1073741824) }} GB
|
||||
{% endif %}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900 dark:text-white">
|
||||
{{ item.file.created_at.strftime('%Y-%m-%d %H:%M') if item.file.created_at else 'N/A' }}
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm">
|
||||
{% if item.session %}
|
||||
<span class="text-gray-900 dark:text-white font-mono text-xs">
|
||||
{{ item.session.id[:8] }}...
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-gray-400">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-right text-sm">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button onclick="downloadFile('{{ item.file.id }}')"
|
||||
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors"
|
||||
title="Download file">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button onclick="viewFileDetails('{{ item.file.id }}')"
|
||||
class="px-3 py-1 text-xs bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
||||
title="View details">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-12">
|
||||
<svg class="w-16 h-16 mx-auto mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-2">No data files yet</p>
|
||||
<p class="text-sm text-gray-400 dark:text-gray-500">Files will appear here after recording sessions</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
function downloadFile(fileId) {
|
||||
// TODO: Implement file download
|
||||
window.location.href = `/api/projects/{{ project_id }}/files/${fileId}/download`;
|
||||
}
|
||||
|
||||
function viewFileDetails(fileId) {
|
||||
// TODO: Implement file details modal
|
||||
alert('File details coming soon: ' + fileId);
|
||||
}
|
||||
</script>
|
||||
@@ -77,13 +77,6 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Check FTP status for all units on load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
{% for unit_item in units %}
|
||||
checkFTPStatus('{{ unit_item.unit.id }}');
|
||||
{% endfor %}
|
||||
});
|
||||
|
||||
async function checkFTPStatus(unitId) {
|
||||
const statusSpan = document.getElementById(`ftp-status-${unitId}`);
|
||||
const enableBtn = document.getElementById(`enable-ftp-${unitId}`);
|
||||
@@ -211,39 +204,23 @@ async function loadFTPFiles(unitId, path) {
|
||||
${file.is_dir ? `
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="event.stopPropagation(); downloadFolderToServer('${unitId}', '${file.path}', '${file.name}')"
|
||||
class="px-3 py-1 text-xs bg-green-600 text-white rounded hover:bg-green-700 transition-colors"
|
||||
title="Download entire folder to server and add to database">
|
||||
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"></path>
|
||||
</svg>
|
||||
To Server (ZIP)
|
||||
</button>
|
||||
<button onclick="event.stopPropagation(); downloadFTPFolder('${unitId}', '${file.path}', '${file.name}')"
|
||||
class="px-3 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
|
||||
title="Download entire folder as ZIP to your computer">
|
||||
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded hover:bg-seismo-navy transition-colors"
|
||||
title="Download folder from device to server and add to project database">
|
||||
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
||||
</svg>
|
||||
To Browser (ZIP)
|
||||
Download & Save
|
||||
</button>
|
||||
</div>
|
||||
` : `
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="downloadToServer('${unitId}', '${file.path}', '${file.name}')"
|
||||
class="px-3 py-1 text-xs bg-green-600 text-white rounded hover:bg-green-700 transition-colors"
|
||||
title="Download to server and add to database">
|
||||
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"></path>
|
||||
</svg>
|
||||
To Server
|
||||
</button>
|
||||
<button onclick="downloadFTPFile('${unitId}', '${file.path}', '${file.name}')"
|
||||
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded hover:bg-seismo-navy transition-colors"
|
||||
title="Download directly to your computer">
|
||||
title="Download file from device to server and add to project database">
|
||||
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
||||
</svg>
|
||||
To Browser
|
||||
Download & Save
|
||||
</button>
|
||||
</div>
|
||||
`}
|
||||
@@ -365,10 +342,10 @@ async function downloadFolderToServer(unitId, remotePath, folderName) {
|
||||
|
||||
if (response.ok) {
|
||||
// Show success message
|
||||
alert(`✓ Folder "${folderName}" downloaded to server successfully as ZIP!\n\nFile ID: ${data.file_id}\nSize: ${formatFileSize(data.file_size)}`);
|
||||
alert(`✓ Folder "${folderName}" downloaded successfully!\n\n${data.file_count} files extracted\nTotal size: ${formatFileSize(data.total_size)}\n\nFiles are now available in the Project Files section below.`);
|
||||
|
||||
// Refresh the downloaded files list
|
||||
htmx.trigger('#project-files', 'refresh');
|
||||
// Refresh the unified files list
|
||||
htmx.trigger('#unified-files', 'refresh');
|
||||
} else {
|
||||
alert('Folder download to server failed: ' + (data.detail || 'Unknown error'));
|
||||
}
|
||||
@@ -409,8 +386,8 @@ async function downloadToServer(unitId, remotePath, fileName) {
|
||||
// Show success message
|
||||
alert(`✓ ${fileName} downloaded to server successfully!\n\nFile ID: ${data.file_id}\nSize: ${formatFileSize(data.file_size)}`);
|
||||
|
||||
// Refresh the downloaded files list
|
||||
htmx.trigger('#project-files', 'refresh');
|
||||
// Refresh the unified files list
|
||||
htmx.trigger('#unified-files', 'refresh');
|
||||
} else {
|
||||
alert('Download to server failed: ' + (data.detail || 'Unknown error'));
|
||||
}
|
||||
@@ -428,4 +405,12 @@ function formatFileSize(bytes) {
|
||||
if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB';
|
||||
return (bytes / 1073741824).toFixed(2) + ' GB';
|
||||
}
|
||||
|
||||
// Check FTP status for all units on load
|
||||
// Use setTimeout to ensure DOM elements exist when HTMX loads this partial
|
||||
setTimeout(function() {
|
||||
{% for unit_item in units %}
|
||||
checkFTPStatus('{{ unit_item.unit.id }}');
|
||||
{% endfor %}
|
||||
}, 100);
|
||||
</script>
|
||||
|
||||
173
templates/partials/projects/unified_files.html
Normal file
173
templates/partials/projects/unified_files.html
Normal file
@@ -0,0 +1,173 @@
|
||||
<!-- Unified Files View - Database + Filesystem -->
|
||||
{% if sessions %}
|
||||
<div class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{% for session_data in sessions %}
|
||||
{% set session = session_data.session %}
|
||||
{% set unit = session_data.unit %}
|
||||
{% set location = session_data.location %}
|
||||
{% set files = session_data.files %}
|
||||
|
||||
{% if files %}
|
||||
<!-- Session Header -->
|
||||
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-900/50">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="font-semibold text-gray-900 dark:text-white">
|
||||
{{ session.started_at.strftime('%Y-%m-%d %H:%M') if session.started_at else 'Unknown Date' }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{% if unit %}{{ unit.id }}{% else %}Unknown Unit{% endif %}
|
||||
{% if location %} @ {{ location.name }}{% endif %}
|
||||
<span class="mx-2">•</span>
|
||||
{{ files|length }} file{{ 's' if files|length != 1 else '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-1 text-xs rounded-full
|
||||
{% if session.status == 'recording' %}bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300
|
||||
{% elif session.status == 'completed' %}bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300
|
||||
{% elif session.status == 'paused' %}bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300
|
||||
{% else %}bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300{% endif %}">
|
||||
{{ session.status or 'unknown' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Files List -->
|
||||
<div class="px-6 py-2">
|
||||
<div class="space-y-1">
|
||||
{% for file_data in files %}
|
||||
{% set file = file_data.file %}
|
||||
{% set exists = file_data.exists_on_disk %}
|
||||
{% set metadata = file_data.metadata %}
|
||||
|
||||
<div class="flex items-center gap-3 px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-lg transition-colors group">
|
||||
<!-- File Icon -->
|
||||
{% if file.file_type == 'audio' %}
|
||||
<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"></path>
|
||||
</svg>
|
||||
{% elif file.file_type == 'archive' %}
|
||||
<svg class="w-6 h-6 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"></path>
|
||||
</svg>
|
||||
{% elif file.file_type == 'log' %}
|
||||
<svg class="w-6 h-6 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
{% elif file.file_type == 'image' %}
|
||||
<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
{% else %}
|
||||
<svg class="w-6 h-6 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
{% endif %}
|
||||
|
||||
<!-- File Info -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="font-medium text-gray-900 dark:text-white truncate">
|
||||
{{ file.file_path.split('/')[-1] if file.file_path else 'Unknown' }}
|
||||
</div>
|
||||
{% if not exists %}
|
||||
<span class="px-2 py-0.5 text-xs bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300 rounded">
|
||||
Missing on disk
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
<!-- File Type Badge -->
|
||||
<span class="px-1.5 py-0.5 rounded font-medium
|
||||
{% if file.file_type == 'audio' %}bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300
|
||||
{% elif file.file_type == 'data' %}bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300
|
||||
{% elif file.file_type == 'log' %}bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300
|
||||
{% elif file.file_type == 'archive' %}bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300
|
||||
{% elif file.file_type == 'image' %}bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-300
|
||||
{% else %}bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300{% endif %}">
|
||||
{{ file.file_type or 'unknown' }}
|
||||
</span>
|
||||
|
||||
<!-- File Size -->
|
||||
<span class="mx-1">•</span>
|
||||
{% if file.file_size_bytes %}
|
||||
{% if file.file_size_bytes < 1024 %}
|
||||
{{ file.file_size_bytes }} B
|
||||
{% elif file.file_size_bytes < 1048576 %}
|
||||
{{ "%.1f"|format(file.file_size_bytes / 1024) }} KB
|
||||
{% elif file.file_size_bytes < 1073741824 %}
|
||||
{{ "%.1f"|format(file.file_size_bytes / 1048576) }} MB
|
||||
{% else %}
|
||||
{{ "%.2f"|format(file.file_size_bytes / 1073741824) }} GB
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Unknown size
|
||||
{% endif %}
|
||||
|
||||
<!-- Download Time -->
|
||||
{% if file.downloaded_at %}
|
||||
<span class="mx-1">•</span>
|
||||
{{ file.downloaded_at.strftime('%Y-%m-%d %H:%M') }}
|
||||
{% endif %}
|
||||
|
||||
<!-- Source Info from Metadata -->
|
||||
{% if metadata.unit_id %}
|
||||
<span class="mx-1">•</span>
|
||||
from {{ metadata.unit_id }}
|
||||
{% endif %}
|
||||
|
||||
<!-- Checksum Indicator -->
|
||||
{% if file.checksum %}
|
||||
<span class="mx-1" title="SHA256: {{ file.checksum[:16] }}...">
|
||||
<svg class="w-3 h-3 inline text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Download Button -->
|
||||
{% if exists %}
|
||||
<div class="opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button onclick="downloadFile('{{ file.id }}')"
|
||||
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
||||
</svg>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Empty State -->
|
||||
<div class="px-6 py-12 text-center">
|
||||
<svg class="w-16 h-16 mx-auto mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-2">No files downloaded yet</p>
|
||||
<p class="text-sm text-gray-400 dark:text-gray-500">
|
||||
Use the FTP browser above to download files from your sound level meters
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
function downloadFile(fileId) {
|
||||
window.location.href = `/api/projects/{{ project_id }}/files/${fileId}/download`;
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user