Add dashcam USB file browser
Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -7,6 +7,101 @@ const deviceStatus = document.getElementById('dash-device-status');
|
||||
const importStatus = document.getElementById('dash-import-status');
|
||||
const scanBtn = document.getElementById('dash-scan-btn');
|
||||
const importBtn = document.getElementById('dash-import-btn');
|
||||
const fileSummary = document.getElementById('dash-file-summary');
|
||||
const fileListBody = document.querySelector('#dashcam-file-list [data-data-list-body]');
|
||||
const filesRefreshBtn = document.getElementById('dash-files-refresh-btn');
|
||||
const deleteAllBtn = document.getElementById('dash-delete-all-btn');
|
||||
|
||||
function fetchJson(url, options) {
|
||||
return fetch(url, options).then(response => response.json().then(data => {
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.status || `HTTP ${response.status}`);
|
||||
}
|
||||
return data;
|
||||
}));
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) {
|
||||
return '0 B';
|
||||
}
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const unit = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
||||
return `${(bytes / (1024 ** unit)).toFixed(unit === 0 ? 0 : 1)} ${units[unit]}`;
|
||||
}
|
||||
|
||||
function createCell(text) {
|
||||
const cell = document.createElement('td');
|
||||
cell.textContent = text;
|
||||
return cell;
|
||||
}
|
||||
|
||||
function renderDashcamFiles(summary) {
|
||||
fileListBody.replaceChildren();
|
||||
if (summary.status !== 'ok') {
|
||||
fileSummary.textContent = summary.message || `Unable to list files: ${summary.status}`;
|
||||
deleteAllBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const files = summary.files || [];
|
||||
|
||||
files.forEach(file => {
|
||||
const row = document.createElement('tr');
|
||||
row.dataset.path = file.path;
|
||||
row.dataset.size = file.size;
|
||||
row.dataset.modified = file.modified_at;
|
||||
row.appendChild(createCell(file.path));
|
||||
row.appendChild(createCell(formatBytes(file.size)));
|
||||
row.appendChild(createCell(new Date(file.modified_at * 1000).toLocaleString()));
|
||||
|
||||
const actions = document.createElement('td');
|
||||
actions.className = 'data-list-actions';
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.type = 'button';
|
||||
deleteButton.className = 'btn btn-sm btn-outline-danger';
|
||||
deleteButton.dataset.deletePath = file.path;
|
||||
deleteButton.dataset.confirm = `Permanently delete "${file.path}" from the dashcam?`;
|
||||
deleteButton.innerHTML = '<i class="fas fa-trash me-1"></i>Delete';
|
||||
actions.appendChild(deleteButton);
|
||||
row.appendChild(actions);
|
||||
fileListBody.appendChild(row);
|
||||
});
|
||||
|
||||
fileSummary.textContent = `${files.length} video(s), ${formatBytes(summary.total_size || 0)} total`;
|
||||
if (summary.errors && summary.errors.length) {
|
||||
fileSummary.textContent += ` (${summary.errors.length} file error(s))`;
|
||||
}
|
||||
deleteAllBtn.disabled = files.length === 0;
|
||||
}
|
||||
|
||||
function refreshDashcamFiles() {
|
||||
fileSummary.textContent = 'Scanning...';
|
||||
filesRefreshBtn.disabled = true;
|
||||
return fetchJson('/api/dash/files')
|
||||
.then(renderDashcamFiles)
|
||||
.catch(err => {
|
||||
fileListBody.replaceChildren();
|
||||
fileSummary.textContent = `Unable to list dashcam files: ${err.message}`;
|
||||
deleteAllBtn.disabled = true;
|
||||
})
|
||||
.finally(() => {
|
||||
filesRefreshBtn.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function deleteDashcamFile(path) {
|
||||
return fetchJson('/api/dash/file', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path }),
|
||||
}).then(result => {
|
||||
fileSummary.textContent = `Deleted ${result.deleted_count} video(s). Refreshing...`;
|
||||
return refreshDashcamFiles();
|
||||
}).catch(err => {
|
||||
fileSummary.textContent = `Delete failed: ${err.message}`;
|
||||
});
|
||||
}
|
||||
|
||||
function describeDevice(device) {
|
||||
const label = device.label ? ` "${device.label}"` : '';
|
||||
@@ -49,6 +144,7 @@ function importDashcam() {
|
||||
text += ` Errors: ${summary.errors.join('; ')}`;
|
||||
}
|
||||
importStatus.textContent = text;
|
||||
refreshDashcamFiles();
|
||||
})
|
||||
.catch(err => {
|
||||
importStatus.textContent = 'Error while importing: ' + err;
|
||||
@@ -60,9 +156,35 @@ function importDashcam() {
|
||||
|
||||
scanBtn.addEventListener('click', scanForDashcam);
|
||||
importBtn.addEventListener('click', importDashcam);
|
||||
filesRefreshBtn.addEventListener('click', refreshDashcamFiles);
|
||||
fileListBody.addEventListener('click', event => {
|
||||
const deleteButton = event.target.closest('[data-delete-path]');
|
||||
if (deleteButton) {
|
||||
deleteButton.disabled = true;
|
||||
deleteDashcamFile(deleteButton.dataset.deletePath).finally(() => {
|
||||
deleteButton.disabled = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
deleteAllBtn.addEventListener('click', () => {
|
||||
if (!window.confirm(deleteAllBtn.dataset.confirm)) {
|
||||
return;
|
||||
}
|
||||
deleteAllBtn.disabled = true;
|
||||
fetchJson('/api/dash/files', { method: 'DELETE' })
|
||||
.then(result => {
|
||||
fileSummary.textContent = `Deleted ${result.deleted_count} video(s). Refreshing...`;
|
||||
return refreshDashcamFiles();
|
||||
})
|
||||
.catch(err => {
|
||||
fileSummary.textContent = `Delete all failed: ${err.message}`;
|
||||
deleteAllBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-scan once when the page loads.
|
||||
scanForDashcam();
|
||||
refreshDashcamFiles();
|
||||
|
||||
// --- Remote server upload status/toggle ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user