feat: Introduce initial web frontend and backend services, and generalize directory configuration in docker-compose.

This commit is contained in:
Bruno Charest 2026-03-24 12:24:43 -04:00
parent 6b03709b30
commit d76ad89f09
4 changed files with 67 additions and 6 deletions

View File

@ -52,7 +52,7 @@ SUPPORTED_EXTENSIONS = {
def load_vault_config() -> Dict[str, Dict[str, Any]]: def load_vault_config() -> Dict[str, Dict[str, Any]]:
"""Read VAULT_N_* env vars and return vault configuration. """Read VAULT_N_* and DIR_N_* env vars and return vault configuration.
Scans environment variables ``VAULT_1_NAME``/``VAULT_1_PATH``, Scans environment variables ``VAULT_1_NAME``/``VAULT_1_PATH``,
``VAULT_2_NAME``/``VAULT_2_PATH``, etc. in sequential order. ``VAULT_2_NAME``/``VAULT_2_PATH``, etc. in sequential order.
@ -67,6 +67,7 @@ def load_vault_config() -> Dict[str, Dict[str, Any]]:
- path: filesystem path (required) - path: filesystem path (required)
- attachmentsPath: relative attachments folder (optional) - attachmentsPath: relative attachments folder (optional)
- scanAttachmentsOnStartup: boolean (default True) - scanAttachmentsOnStartup: boolean (default True)
- type: "VAULT" or "DIR"
""" """
vaults: Dict[str, Dict[str, Any]] = {} vaults: Dict[str, Dict[str, Any]] = {}
n = 1 n = 1
@ -84,8 +85,25 @@ def load_vault_config() -> Dict[str, Dict[str, Any]]:
"path": path, "path": path,
"attachmentsPath": attachments_path, "attachmentsPath": attachments_path,
"scanAttachmentsOnStartup": scan_attachments, "scanAttachmentsOnStartup": scan_attachments,
"type": "VAULT"
} }
n += 1 n += 1
n = 1
while True:
name = os.environ.get(f"DIR_{n}_NAME")
path = os.environ.get(f"DIR_{n}_PATH")
if not name or not path:
break
vaults[name] = {
"path": path,
"attachmentsPath": None,
"scanAttachmentsOnStartup": False,
"type": "DIR"
}
n += 1
return vaults return vaults

View File

@ -59,6 +59,7 @@ class VaultInfo(BaseModel):
name: str = Field(description="Display name of the vault") name: str = Field(description="Display name of the vault")
file_count: int = Field(description="Number of indexed files") file_count: int = Field(description="Number of indexed files")
tag_count: int = Field(description="Number of unique tags") tag_count: int = Field(description="Number of unique tags")
type: str = Field(default="VAULT", description="Type of the vault mapping (VAULT or DIR)")
class BrowseItem(BaseModel): class BrowseItem(BaseModel):
@ -603,10 +604,12 @@ async def api_vaults(current_user=Depends(require_auth)):
result = [] result = []
for name, data in index.items(): for name, data in index.items():
if "*" in user_vaults or name in user_vaults: if "*" in user_vaults or name in user_vaults:
v_type = vault_config.get(name, {}).get("type", "VAULT")
result.append({ result.append({
"name": name, "name": name,
"file_count": len(data["files"]), "file_count": len(data["files"]),
"tag_count": len(data["tags"]), "tag_count": len(data["tags"]),
"type": v_type,
}) })
return result return result

View File

@ -39,8 +39,8 @@ services:
- VAULT_4_PATH=/vaults/Obsidian_WORKOUT - VAULT_4_PATH=/vaults/Obsidian_WORKOUT
- VAULT_5_NAME=Sessions - VAULT_5_NAME=Sessions
- VAULT_5_PATH=/vaults/SessionsManager - VAULT_5_PATH=/vaults/SessionsManager
- VAULT_6_NAME=Bruno - DIR_1_NAME=Bruno
- VAULT_6_PATH=/vaults/bruno - DIR_1_PATH=/vaults/bruno
# Auth configuration (uncomment to enable) # Auth configuration (uncomment to enable)
- OBSIGATE_AUTH_ENABLED=true - OBSIGATE_AUTH_ENABLED=true
- OBSIGATE_ADMIN_USER=admin - OBSIGATE_ADMIN_USER=admin

View File

@ -1400,7 +1400,7 @@
vaultsToShow.forEach((v) => { vaultsToShow.forEach((v) => {
const vaultItem = el("div", { class: "tree-item vault-item", "data-vault": v.name }, [ const vaultItem = el("div", { class: "tree-item vault-item", "data-vault": v.name }, [
icon("chevron-right", 14), icon("chevron-right", 14),
icon("database", 16), getVaultIcon(v.name, 16),
document.createTextNode(` ${v.name} `), document.createTextNode(` ${v.name} `),
smallBadge(v.file_count), smallBadge(v.file_count),
]); ]);
@ -1457,7 +1457,7 @@
// Sidebar tree entry // Sidebar tree entry
const vaultItem = el("div", { class: "tree-item vault-item", "data-vault": v.name }, [ const vaultItem = el("div", { class: "tree-item vault-item", "data-vault": v.name }, [
icon("chevron-right", 14), icon("chevron-right", 14),
icon("database", 16), getVaultIcon(v.name, 16),
document.createTextNode(` ${v.name} `), document.createTextNode(` ${v.name} `),
smallBadge(v.file_count), smallBadge(v.file_count),
]); ]);
@ -1736,7 +1736,7 @@
entries.sort((a, b) => a.path.localeCompare(b.path, undefined, { sensitivity: "base" })); entries.sort((a, b) => a.path.localeCompare(b.path, undefined, { sensitivity: "base" }));
const vaultHeader = el("div", { class: "tree-item vault-item filter-results-header", "data-vault": vaultName }, [ const vaultHeader = el("div", { class: "tree-item vault-item filter-results-header", "data-vault": vaultName }, [
icon("database", 16), getVaultIcon(vaultName, 16),
document.createTextNode(` ${vaultName} `), document.createTextNode(` ${vaultName} `),
smallBadge(entries.length), smallBadge(entries.length),
]); ]);
@ -3296,6 +3296,46 @@
return s; return s;
} }
function getVaultIcon(vaultName, size = 16) {
const v = allVaults.find((val) => val.name === vaultName);
const type = v ? v.type : "VAULT";
if (type === "DIR") {
const i = icon("folder", size);
i.style.color = "#eab308"; // yellow tint
return i;
} else {
const purple = "#8b5cf6";
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNS, "svg");
svg.setAttribute("xmlns", svgNS);
svg.setAttribute("width", size);
svg.setAttribute("height", size);
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("fill", "none");
svg.setAttribute("stroke", purple);
svg.setAttribute("stroke-width", "2");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
svg.classList.add("icon");
const path1 = document.createElementNS(svgNS, "path");
path1.setAttribute("d", "M6 3h12l4 6-10 12L2 9z");
const path2 = document.createElementNS(svgNS, "path");
path2.setAttribute("d", "M11 3 8 9l4 12");
const path3 = document.createElementNS(svgNS, "path");
path3.setAttribute("d", "M12 21l4-12-3-6");
const path4 = document.createElementNS(svgNS, "path");
path4.setAttribute("d", "M2 9h20");
svg.appendChild(path1);
svg.appendChild(path2);
svg.appendChild(path3);
svg.appendChild(path4);
return svg;
}
}
function makeBreadcrumbSpan(text, onClick) { function makeBreadcrumbSpan(text, onClick) {
const s = document.createElement("span"); const s = document.createElement("span");
s.textContent = text; s.textContent = text;