fix: vault home recursive + backup viewer fixes + AI status at startup
- Vault home (#12): recursive file listing (rglob), rel_dir in metadata, 'recursif' badge, updated roadmap description - Backup viewer: fix close button (inline onclick + event delegation fallback for text nodes), improve error messages - Backup API: remove response_model to prevent Pydantic 500 errors, add try/except + logging on backups/diff/restore endpoints, per-entry error handling in _list_backup_files, encoding safety (errors='replace') on read_text - AI status: call /api/ai/status at startup in _initAIStatus instead of waiting for user click on sync badge
This commit is contained in:
+21
-9
@@ -59,7 +59,8 @@ export async function openBackupViewer(vault, path) {
|
||||
const data = await api(`/api/file/${encodeURIComponent(vault)}/backups?path=${encodeURIComponent(path)}`);
|
||||
state.backups = data.backups || [];
|
||||
} catch (err) {
|
||||
setBody(renderError('Failed to load backups'));
|
||||
const msg = err?.detail || err?.message || 'Erreur inconnue';
|
||||
setBody(renderError(`Erreur API: ${msg}`));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -95,6 +96,17 @@ function createOverlay() {
|
||||
panel.id = 'backup-viewer-panel';
|
||||
overlay.appendChild(panel);
|
||||
|
||||
// Event delegation fallback for close button (in case inline onclick fails)
|
||||
panel.addEventListener('click', (e) => {
|
||||
// e.target might be a text node (e.g. "×") — use parentElement as fallback
|
||||
const target = e.target.nodeType === Node.TEXT_NODE ? e.target.parentElement : e.target;
|
||||
const closeBtn = target && target.closest ? target.closest('.backup-close-btn') : null;
|
||||
if (closeBtn) {
|
||||
e.stopPropagation();
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
return overlay;
|
||||
}
|
||||
|
||||
@@ -113,6 +125,9 @@ function close() {
|
||||
}
|
||||
}
|
||||
|
||||
// Expose globally for inline onclick handlers (bypasses text-node target issues)
|
||||
window._backupViewerClose = close;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rendering
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -121,7 +136,7 @@ function renderLoading() {
|
||||
return `
|
||||
<div class="backup-header">
|
||||
<h3>Backups</h3>
|
||||
<button class="backup-close-btn" title="Fermer">×</button>
|
||||
<button class="backup-close-btn" title="Fermer" onclick="window._backupViewerClose()">×</button>
|
||||
</div>
|
||||
<div class="backup-body">
|
||||
<div class="tree-loading" style="padding:2rem;text-align:center">
|
||||
@@ -143,10 +158,10 @@ function renderError(msg) {
|
||||
}
|
||||
|
||||
function renderEmpty() {
|
||||
return `
|
||||
return \`
|
||||
<div class="backup-header">
|
||||
<h3>Backups — ${escapeHtml(state.path)}</h3>
|
||||
<button class="backup-close-btn" title="Fermer">×</button>
|
||||
<h3>Backups — \${escapeHtml(state.path)}</h3>
|
||||
<button class="backup-close-btn" title="Fermer" onclick="window._backupViewerClose()">×</button>
|
||||
</div>
|
||||
<div class="backup-body" style="text-align:center;padding:2rem;color:var(--text-muted)">
|
||||
<p>Aucun backup disponible pour ce fichier.</p>
|
||||
@@ -177,7 +192,7 @@ function renderMainView() {
|
||||
</div>
|
||||
<div class="backup-header-right">
|
||||
<span class="backup-count">${b.length} backup${b.length > 1 ? 's' : ''}</span>
|
||||
<button class="backup-close-btn" title="Fermer">×</button>
|
||||
<button class="backup-close-btn" title="Fermer" onclick="window._backupViewerClose()">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -220,9 +235,6 @@ function bindEvents() {
|
||||
const panel = document.getElementById('backup-viewer-panel');
|
||||
if (!panel) return;
|
||||
|
||||
// Close button
|
||||
panel.querySelector('.backup-close-btn')?.addEventListener('click', close);
|
||||
|
||||
// Left select
|
||||
const leftSelect = document.getElementById('backup-left-select');
|
||||
leftSelect?.addEventListener('change', async () => {
|
||||
|
||||
+14
-12
@@ -1,6 +1,7 @@
|
||||
/* ObsiGate — Sync: SSE client + PWA registration */
|
||||
import { state } from './state.js';
|
||||
import { showToast } from './ui.js';
|
||||
import { api } from './auth.js';
|
||||
import { loadVaults, loadTags, refreshTagsForContext, refreshSidebarTreePreservingState } from './sidebar.js';
|
||||
import { loadRecentFiles } from './config.js';
|
||||
import { openFile } from './viewer.js';
|
||||
@@ -339,16 +340,16 @@ function _renderSyncPanel(panel) {
|
||||
let _aiProcessing = false;
|
||||
let _aiConfigured = null; // null = unknown, true/false
|
||||
|
||||
/** Check if AI is configured by looking for the AI toolbar in the DOM.
|
||||
* The toolbar is only rendered when the AI API responds successfully. */
|
||||
function _checkAIStatus() {
|
||||
const container = document.getElementById('ai-toolbar-container');
|
||||
if (container && container.children.length > 0) {
|
||||
_aiConfigured = true;
|
||||
} else if (_aiConfigured === null) {
|
||||
// Keep unknown state until we have evidence
|
||||
_aiConfigured = null;
|
||||
/** Check if AI is configured by calling the /api/ai/status endpoint.
|
||||
* Updates _aiConfigured and refreshes the panel if open. */
|
||||
async function _checkAIStatus() {
|
||||
try {
|
||||
const status = await api('/api/ai/status');
|
||||
_aiConfigured = status.configured === true;
|
||||
} catch {
|
||||
_aiConfigured = false;
|
||||
}
|
||||
_updateAIStatusInPanel();
|
||||
}
|
||||
|
||||
function _renderAIStatusSection(panel) {
|
||||
@@ -420,6 +421,9 @@ function _setBadgeAIProcessing(active) {
|
||||
|
||||
/** Initialize AI status tracking. */
|
||||
function _initAIStatus() {
|
||||
// Check AI status immediately at startup
|
||||
_checkAIStatus();
|
||||
|
||||
// Listen for AI processing events from ai.js
|
||||
document.addEventListener('ai-processing-start', () => {
|
||||
_aiProcessing = true;
|
||||
@@ -433,13 +437,11 @@ function _initAIStatus() {
|
||||
_updateAIStatusInPanel();
|
||||
});
|
||||
|
||||
// Check AI status on first sync panel open
|
||||
// Re-check AI status when sync panel opens (in case config changed)
|
||||
const badge = document.getElementById('sync-badge');
|
||||
if (badge) {
|
||||
const origClick = badge.onclick;
|
||||
badge.addEventListener('click', () => {
|
||||
_checkAIStatus();
|
||||
_updateAIStatusInPanel();
|
||||
}, { once: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
* Vault Home Page module for ObsiGate.
|
||||
*
|
||||
* When a specific vault is selected (not "All"), replaces the dashboard
|
||||
* with a file listing sorted by modification time (newest first).
|
||||
* with a recursive file listing sorted by modification time (newest first).
|
||||
* Files from the selected directory AND all its subdirectories are shown
|
||||
* with their relative path displayed in the metadata line.
|
||||
* The listing updates automatically when browsing into subdirectories.
|
||||
*
|
||||
* The visual style mirrors the search results (.search-result-item) for
|
||||
@@ -184,7 +186,7 @@ function buildBreadcrumbHTML(parentEl, vaultName, dirPath) {
|
||||
|
||||
async function loadAndRender(vaultName, dirPath, container) {
|
||||
try {
|
||||
const url = `/api/vault/${encodeURIComponent(vaultName)}/files?dir=${encodeURIComponent(dirPath)}&limit=200`;
|
||||
const url = `/api/vault/${encodeURIComponent(vaultName)}/files?dir=${encodeURIComponent(dirPath)}&limit=200&recursive=true`;
|
||||
const data = await api(url);
|
||||
|
||||
if (!data.files || data.files.length === 0) {
|
||||
@@ -204,7 +206,15 @@ async function loadAndRender(vaultName, dirPath, container) {
|
||||
]);
|
||||
header.appendChild(count);
|
||||
|
||||
// Sort dropdown (already sorted by mtime from backend)
|
||||
// Recursive indicator
|
||||
if (data.recursive) {
|
||||
const recursiveBadge = el('span', { class: 'vault-home-recursive-badge' }, [
|
||||
document.createTextNode('récursif'),
|
||||
]);
|
||||
header.appendChild(recursiveBadge);
|
||||
}
|
||||
|
||||
// Directory label
|
||||
const dirLabel = dirPath ? `/${dirPath}` : ' (racine)';
|
||||
const dirInfo = el('span', { class: 'vault-home-dir-info' }, [
|
||||
document.createTextNode(dirLabel),
|
||||
@@ -233,8 +243,16 @@ async function loadAndRender(vaultName, dirPath, container) {
|
||||
hour: '2-digit', minute: '2-digit',
|
||||
});
|
||||
|
||||
// Build metadata line: rel_dir (if any) + date + size
|
||||
const metaParts = [];
|
||||
if (f.rel_dir) {
|
||||
metaParts.push(f.rel_dir + '/');
|
||||
}
|
||||
metaParts.push(dateStr);
|
||||
metaParts.push(formatSize(f.size));
|
||||
|
||||
const metaDiv = el('div', { class: 'search-result-vault' }, [
|
||||
document.createTextNode(`${dateStr} · ${formatSize(f.size)}`),
|
||||
document.createTextNode(metaParts.join(' · ')),
|
||||
]);
|
||||
|
||||
const item = el('div', {
|
||||
|
||||
Reference in New Issue
Block a user