From 8a8c4073e5541fb3d3bfc6b495dc3cc9623a7849 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Tue, 14 Jul 2026 11:09:47 +0000 Subject: [PATCH] Add Nextcloud directory browser to backup_path field in Settings The Backup Directory field in the Nextcloud integration card gains a Browse button that opens an inline panel with: breadcrumb navigation, scrollable directory list, and a Select button to pick the current path. Falls back to manual text entry. Co-Authored-By: Claude Sonnet 4.6 --- src/pages/AdminSettings.tsx | 157 +++++++++++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 1 deletion(-) diff --git a/src/pages/AdminSettings.tsx b/src/pages/AdminSettings.tsx index 4cd980f..415268b 100644 --- a/src/pages/AdminSettings.tsx +++ b/src/pages/AdminSettings.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { CheckCircle, XCircle, RefreshCw, ChevronDown, ChevronUp, Clock } from 'lucide-react' +import { CheckCircle, XCircle, RefreshCw, ChevronDown, ChevronUp, Clock, Folder, FolderOpen, ChevronRight } from 'lucide-react' import { Sidebar } from '../components/Sidebar' import type { User } from '../types' @@ -113,6 +113,11 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o const [wfLocations, setWfLocations] = useState<{ id: string; name: string; short_name: string | null }[]>([]) const [fetchingLocs, setFetchingLocs] = useState(false) const [locError, setLocError] = useState(null) + const [ncBrowseOpen, setNcBrowseOpen] = useState(false) + const [ncBrowsePath, setNcBrowsePath] = useState('/') + const [ncBrowseDirs, setNcBrowseDirs] = useState<{ name: string; path: string }[]>([]) + const [ncBrowseLoading, setNcBrowseLoading] = useState(false) + const [ncBrowseError, setNcBrowseError] = useState(null) function openForm() { const initial: Record = {} @@ -120,9 +125,28 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o setForm(initial) setChanging(new Set()) setTestResult(null) + setNcBrowseOpen(false) setOpen(true) } + async function ncBrowse(path: string) { + setNcBrowseLoading(true) + setNcBrowseError(null) + setNcBrowsePath(path) + try { + const res = await fetch(`/settings/api/integrations/nextcloud/browse?path=${encodeURIComponent(path)}`, { + credentials: 'include', + }) + const data = await res.json() + if (!res.ok) { setNcBrowseError(data.error || `Error ${res.status}`); return } + setNcBrowseDirs(data.dirs) + } catch { + setNcBrowseError('Network error') + } finally { + setNcBrowseLoading(false) + } + } + function setField(key: string, value: string) { setForm(prev => ({ ...prev, [key]: value })) } @@ -193,6 +217,137 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o {/* Config fields (plaintext) */} {Object.entries(integration.config).map(([k]) => { + if (integration.slug === 'nextcloud' && k === 'backup_path') { + const pathSegments = ncBrowsePath === '/' ? [] : ncBrowsePath.split('/').filter(Boolean) + return ( +
+ + Backup Directory + +
+ setField(k, e.target.value)} + placeholder="e.g. HNF-Backups" style={{ ...inputStyle, flex: 1 }} /> + +
+ + {ncBrowseOpen && ( +
+ {/* Breadcrumb */} +
+ + {pathSegments.map((seg, i) => { + const segPath = '/' + pathSegments.slice(0, i + 1).join('/') + const isLast = i === pathSegments.length - 1 + return ( + + + + + ) + })} +
+ + {/* Directory list */} +
+ {ncBrowseLoading && ( +
+ Loading… +
+ )} + {ncBrowseError && ( +
+ {ncBrowseError} +
+ )} + {!ncBrowseLoading && !ncBrowseError && ncBrowseDirs.length === 0 && ( +
+ No subdirectories here +
+ )} + {!ncBrowseLoading && ncBrowseDirs.map(dir => ( + + ))} +
+ + {/* Footer: select current or cancel */} +
+ + {ncBrowsePath} + +
+ + +
+
+
+ )} +
+ ) + } if (integration.slug === 'workforce' && k === 'location_id') { return (