feat: add per-vault reindexing with selective reload based on modified vaults, simplify hidden files whitelist logic to include all sub-hidden files when parent is whitelisted, and auto-uncheck includeHidden when adding whitelist items
This commit is contained in:
parent
80e2a7fc53
commit
ac962bd416
@ -426,6 +426,80 @@ async def reload_index() -> Dict[str, Any]:
|
|||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
|
||||||
|
async def reload_single_vault(vault_name: str) -> Dict[str, Any]:
|
||||||
|
"""Force a re-index of a single vault and return its statistics.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
vault_name: Name of the vault to reindex.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with vault statistics (file_count, tag_count).
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If vault_name is not found in configuration.
|
||||||
|
"""
|
||||||
|
global vault_config
|
||||||
|
|
||||||
|
# Reload vault config from env vars
|
||||||
|
vault_config.update(load_vault_config())
|
||||||
|
|
||||||
|
# Merge with saved settings
|
||||||
|
from backend.vault_settings import get_vault_setting
|
||||||
|
saved_settings = get_vault_setting(vault_name)
|
||||||
|
|
||||||
|
if vault_name not in vault_config:
|
||||||
|
raise ValueError(f"Vault '{vault_name}' not found in configuration")
|
||||||
|
|
||||||
|
config = vault_config[vault_name]
|
||||||
|
|
||||||
|
# Override with saved settings if present
|
||||||
|
if saved_settings:
|
||||||
|
if "includeHidden" in saved_settings:
|
||||||
|
config["includeHidden"] = saved_settings["includeHidden"]
|
||||||
|
if "hiddenWhitelist" in saved_settings:
|
||||||
|
config["hiddenWhitelist"] = saved_settings["hiddenWhitelist"]
|
||||||
|
|
||||||
|
# Remove old vault data from index structures
|
||||||
|
await remove_vault_from_index(vault_name)
|
||||||
|
|
||||||
|
# Re-add the vault with updated configuration
|
||||||
|
vault_path = config["path"]
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
vault_data = await loop.run_in_executor(None, _scan_vault, vault_name, vault_path, config)
|
||||||
|
vault_data["config"] = config
|
||||||
|
|
||||||
|
# Build lookup entries for the vault
|
||||||
|
new_lookup_entries: Dict[str, List[Dict[str, str]]] = {}
|
||||||
|
for f in vault_data["files"]:
|
||||||
|
entry = {"vault": vault_name, "path": f["path"]}
|
||||||
|
fname = f["path"].rsplit("/", 1)[-1].lower()
|
||||||
|
fpath_lower = f["path"].lower()
|
||||||
|
for key in (fname, fpath_lower):
|
||||||
|
if key not in new_lookup_entries:
|
||||||
|
new_lookup_entries[key] = []
|
||||||
|
new_lookup_entries[key].append(entry)
|
||||||
|
|
||||||
|
async_lock = _get_async_lock()
|
||||||
|
async with async_lock:
|
||||||
|
with _index_lock:
|
||||||
|
index[vault_name] = vault_data
|
||||||
|
for key, entries in new_lookup_entries.items():
|
||||||
|
if key not in _file_lookup:
|
||||||
|
_file_lookup[key] = []
|
||||||
|
_file_lookup[key].extend(entries)
|
||||||
|
path_index[vault_name] = vault_data.get("paths", [])
|
||||||
|
global _index_generation
|
||||||
|
_index_generation += 1
|
||||||
|
|
||||||
|
# Rebuild attachment index for this vault only
|
||||||
|
from backend.attachment_indexer import build_attachment_index
|
||||||
|
await build_attachment_index({vault_name: config})
|
||||||
|
|
||||||
|
stats = {"file_count": len(vault_data["files"]), "tag_count": len(vault_data["tags"])}
|
||||||
|
logger.info(f"Vault '{vault_name}' reindexed: {stats['file_count']} files, {stats['tag_count']} tags")
|
||||||
|
return stats
|
||||||
|
|
||||||
|
|
||||||
def get_vault_names() -> List[str]:
|
def get_vault_names() -> List[str]:
|
||||||
"""Return the list of all indexed vault names."""
|
"""Return the list of all indexed vault names."""
|
||||||
return list(index.keys())
|
return list(index.keys())
|
||||||
|
|||||||
@ -1198,6 +1198,28 @@ async def api_reload(current_user=Depends(require_admin)):
|
|||||||
return {"status": "ok", "vaults": stats}
|
return {"status": "ok", "vaults": stats}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/index/reload/{vault_name}")
|
||||||
|
async def api_reload_vault(vault_name: str, current_user=Depends(require_admin)):
|
||||||
|
"""Force a re-index of a single vault.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
vault_name: Name of the vault to reindex.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with vault statistics.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from backend.indexer import reload_single_vault
|
||||||
|
stats = await reload_single_vault(vault_name)
|
||||||
|
await sse_manager.broadcast("vault_reloaded", {
|
||||||
|
"vault": vault_name,
|
||||||
|
"stats": stats,
|
||||||
|
})
|
||||||
|
return {"status": "ok", "vault": vault_name, "stats": stats}
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# SSE endpoint — Server-Sent Events stream
|
# SSE endpoint — Server-Sent Events stream
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -5,6 +5,12 @@ from typing import Dict, Any, Tuple
|
|||||||
def should_include_path(rel_parts: Tuple[str, ...], vault_config: Dict[str, Any]) -> bool:
|
def should_include_path(rel_parts: Tuple[str, ...], vault_config: Dict[str, Any]) -> bool:
|
||||||
"""Check if a path should be included based on hidden files configuration.
|
"""Check if a path should be included based on hidden files configuration.
|
||||||
|
|
||||||
|
Simplified logic:
|
||||||
|
- If includeHidden=True: include ALL hidden files/folders recursively
|
||||||
|
- If includeHidden=False: check whitelist
|
||||||
|
- If a whitelisted folder is in the path, include ALL sub-hidden files/folders
|
||||||
|
- Otherwise, exclude the path
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
rel_parts: Tuple of path parts relative to vault root
|
rel_parts: Tuple of path parts relative to vault root
|
||||||
vault_config: Vault configuration dict with includeHidden and hiddenWhitelist
|
vault_config: Vault configuration dict with includeHidden and hiddenWhitelist
|
||||||
@ -23,14 +29,14 @@ def should_include_path(rel_parts: Tuple[str, ...], vault_config: Dict[str, Any]
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
if include_hidden:
|
if include_hidden:
|
||||||
# Include all hidden files/folders
|
# Include all hidden files/folders recursively
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check if ALL hidden parts are in the whitelist
|
# Check if the FIRST hidden part is in the whitelist
|
||||||
# If any hidden part is NOT in the whitelist, exclude the path
|
# If yes, include all sub-hidden files/folders automatically
|
||||||
for hidden_part in hidden_parts:
|
first_hidden = hidden_parts[0]
|
||||||
if hidden_part not in hidden_whitelist:
|
if first_hidden in hidden_whitelist:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# First hidden part not in whitelist, exclude
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# All hidden parts are in the whitelist
|
|
||||||
return True
|
|
||||||
|
|||||||
@ -3231,6 +3231,9 @@
|
|||||||
|
|
||||||
// --- Hidden Files Configuration ---
|
// --- Hidden Files Configuration ---
|
||||||
|
|
||||||
|
// Track which vaults have been modified
|
||||||
|
let modifiedVaults = new Set();
|
||||||
|
|
||||||
async function loadHiddenFilesSettings() {
|
async function loadHiddenFilesSettings() {
|
||||||
const container = document.getElementById("hidden-files-vault-list");
|
const container = document.getElementById("hidden-files-vault-list");
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@ -3290,7 +3293,7 @@
|
|||||||
document.createTextNode("Liste blanche")
|
document.createTextNode("Liste blanche")
|
||||||
]);
|
]);
|
||||||
const whitelistHint = el("span", { class: "config-hint", style: "display:block;margin-bottom:8px" }, [
|
const whitelistHint = el("span", { class: "config-hint", style: "display:block;margin-bottom:8px" }, [
|
||||||
document.createTextNode("Dossiers cachés spécifiques à afficher (ex: .obsidian, .github)")
|
document.createTextNode("Dossiers cachés spécifiques à afficher (tous les sous-dossiers cachés seront inclus)")
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const whitelistItems = el("div", { class: "hidden-files-whitelist-items", id: `whitelist-items-${vault.name}` });
|
const whitelistItems = el("div", { class: "hidden-files-whitelist-items", id: `whitelist-items-${vault.name}` });
|
||||||
@ -3330,10 +3333,20 @@
|
|||||||
// Event listeners
|
// Event listeners
|
||||||
const addBtn = addRow.querySelector("button");
|
const addBtn = addRow.querySelector("button");
|
||||||
const input = addRow.querySelector("input");
|
const input = addRow.querySelector("input");
|
||||||
|
const checkbox = toggleRow.querySelector("input[type='checkbox']");
|
||||||
|
|
||||||
addBtn.addEventListener("click", () => addWhitelistItem(vault.name));
|
addBtn.addEventListener("click", () => {
|
||||||
|
addWhitelistItem(vault.name);
|
||||||
|
modifiedVaults.add(vault.name);
|
||||||
|
});
|
||||||
input.addEventListener("keypress", (e) => {
|
input.addEventListener("keypress", (e) => {
|
||||||
if (e.key === "Enter") addWhitelistItem(vault.name);
|
if (e.key === "Enter") {
|
||||||
|
addWhitelistItem(vault.name);
|
||||||
|
modifiedVaults.add(vault.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
checkbox.addEventListener("change", () => {
|
||||||
|
modifiedVaults.add(vault.name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -3353,6 +3366,7 @@
|
|||||||
const removeBtn = item.querySelector("button");
|
const removeBtn = item.querySelector("button");
|
||||||
removeBtn.addEventListener("click", () => {
|
removeBtn.addEventListener("click", () => {
|
||||||
item.remove();
|
item.remove();
|
||||||
|
modifiedVaults.add(vaultName);
|
||||||
});
|
});
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
@ -3361,6 +3375,7 @@
|
|||||||
function addWhitelistItem(vaultName) {
|
function addWhitelistItem(vaultName) {
|
||||||
const input = document.getElementById(`whitelist-input-${vaultName}`);
|
const input = document.getElementById(`whitelist-input-${vaultName}`);
|
||||||
const container = document.getElementById(`whitelist-items-${vaultName}`);
|
const container = document.getElementById(`whitelist-items-${vaultName}`);
|
||||||
|
const includeHiddenCheckbox = document.getElementById(`hidden-include-${vaultName}`);
|
||||||
|
|
||||||
if (!input || !container) return;
|
if (!input || !container) return;
|
||||||
|
|
||||||
@ -3378,6 +3393,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-uncheck "Afficher tous les fichiers cachés" when adding to whitelist
|
||||||
|
if (includeHiddenCheckbox && includeHiddenCheckbox.checked) {
|
||||||
|
includeHiddenCheckbox.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
const item = createWhitelistItem(vaultName, normalizedValue);
|
const item = createWhitelistItem(vaultName, normalizedValue);
|
||||||
container.appendChild(item);
|
container.appendChild(item);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
@ -3414,6 +3434,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
modifiedVaults.clear();
|
||||||
showToast("✓ Paramètres sauvegardés", "success");
|
showToast("✓ Paramètres sauvegardés", "success");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to save hidden files settings:", err);
|
console.error("Failed to save hidden files settings:", err);
|
||||||
@ -3434,9 +3455,18 @@
|
|||||||
// Update button text to show reindexing phase
|
// Update button text to show reindexing phase
|
||||||
if (btn) { btn.textContent = "⏳ Réindexation..."; }
|
if (btn) { btn.textContent = "⏳ Réindexation..."; }
|
||||||
|
|
||||||
// Then trigger reindex with saved settings
|
// Reindex only modified vaults if any, otherwise reindex all
|
||||||
|
if (modifiedVaults.size > 0) {
|
||||||
|
const vaultsToReindex = Array.from(modifiedVaults);
|
||||||
|
for (const vaultName of vaultsToReindex) {
|
||||||
|
await api(`/api/index/reload/${encodeURIComponent(vaultName)}`);
|
||||||
|
}
|
||||||
|
showToast(`✓ Réindexation terminée (${vaultsToReindex.length} vault${vaultsToReindex.length > 1 ? 's' : ''})`, "success");
|
||||||
|
} else {
|
||||||
await api("/api/index/reload");
|
await api("/api/index/reload");
|
||||||
showToast("✓ Réindexation terminée", "success");
|
showToast("✓ Réindexation terminée", "success");
|
||||||
|
}
|
||||||
|
|
||||||
loadDiagnostics();
|
loadDiagnostics();
|
||||||
await Promise.all([loadVaults(), loadTags()]);
|
await Promise.all([loadVaults(), loadTags()]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user