From 4ee422a5e8433ed88657a97081052b2bc81fb012 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Thu, 30 Apr 2026 17:57:00 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20AMPP=20folder=20picker=20UI=20=E2=80=94?= =?UTF-8?q?=20load=20folders,=20pick=20target,=20queue=20placement=20on=20?= =?UTF-8?q?upload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html | 96 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/public/index.html b/public/index.html index 8cadc0b..33ac30c 100644 --- a/public/index.html +++ b/public/index.html @@ -485,6 +485,21 @@ body::before{content:'';position:fixed;inset:0;background:radial-gradient(ellips +
+
+ AMPP Destination Folder + — optional + +
+ +
+
Click ↻ Refresh to load AMPP folders
+
+
+ AMPP folder: (none — skip placement) +
+
+
Files
📂 @@ -816,6 +831,9 @@ let currentUser = localStorage.getItem('dw_user') || null; let currentRole = localStorage.getItem('dw_role') || null; let uploadMode = 'http'; let selectedPrefix = ''; +let selectedAmppFolderId = ''; +let selectedAmppFolderName = ''; +let amppFolderCache = []; let folderTree = []; let selectedFiles = []; @@ -861,6 +879,7 @@ function showApp() { loadS3Config(); loadRelayConfig(); loadAmppConfig(); loadUsers(); loadShareLinks(); populateSlFolderSelect(); } loadFolders(); + loadAmppFolders(); loadAmppJobs(); } @@ -1065,6 +1084,76 @@ async function deleteFolder(pathArr) { } catch(e) { showToast(e.message,'error'); } } +// ============================================================ +// AMPP FOLDER PICKER +// ============================================================ +async function loadAmppFolders() { + const box = document.getElementById('ampp-folder-box'); + const statusEl = document.getElementById('ampp-folder-status'); + if (!box) return; + box.innerHTML = '
Loading AMPP folders…
'; + if (statusEl) statusEl.textContent = '— loading…'; + try { + const d = await api('GET', '/api/ampp/folders/list'); + if (!d.success) { + if (d.error && d.error.toLowerCase().includes('not configured')) { + box.innerHTML = '
AMPP not configured — set up in Admin → AMPP
'; + if (statusEl) statusEl.textContent = '— not configured'; + } else { + box.innerHTML = `
Error: ${esc(d.error||'Failed to load folders')}
`; + if (statusEl) statusEl.textContent = '— error'; + } + return; + } + amppFolderCache = d.folders || []; + if (statusEl) statusEl.textContent = `— ${amppFolderCache.length} folder${amppFolderCache.length!==1?'s':''}`; + renderAmppFolderList(); + } catch(e) { + box.innerHTML = `
Error: ${esc(e.message)}
`; + if (statusEl) statusEl.textContent = '— error'; + } +} + +function renderAmppFolderList() { + const box = document.getElementById('ampp-folder-box'); + if (!box) return; + box.innerHTML = ''; + const searchEl = document.getElementById('ampp-folder-search'); + const filter = (searchEl ? searchEl.value.trim().toLowerCase() : ''); + const folders = amppFolderCache.filter(f => !filter || (f.path||f.name||'').toLowerCase().includes(filter)); + + // "None" row — always first + const noneRow = document.createElement('div'); + noneRow.className = 'folder-tree-row' + (selectedAmppFolderId === '' ? ' active' : ''); + noneRow.innerHTML = `🚫(no AMPP placement)`; + noneRow.onclick = () => { selectedAmppFolderId = ''; selectedAmppFolderName = ''; updateAmppFolderDisplay(); renderAmppFolderList(); }; + box.appendChild(noneRow); + + if (!folders.length && amppFolderCache.length > 0) { + box.innerHTML += '
No folders match your search
'; + return; + } + + // Sort by path/name + const sorted = [...folders].sort((a, b) => (a.path||a.name||'').localeCompare(b.path||b.name||'')); + sorted.forEach(f => { + const id = f.id || f['folder:id'] || ''; + const displayName = f.path || f.name || f['name:text'] || id; + const depth = (displayName.match(/\//g)||[]).length; + const row = document.createElement('div'); + row.className = 'folder-tree-row' + (selectedAmppFolderId === id ? ' active' : ''); + row.style.paddingLeft = (0.75 + depth * 1.0) + 'rem'; + row.innerHTML = `📁${esc(displayName)}`; + row.onclick = () => { selectedAmppFolderId = id; selectedAmppFolderName = displayName; updateAmppFolderDisplay(); renderAmppFolderList(); }; + box.appendChild(row); + }); +} + +function updateAmppFolderDisplay() { + const el = document.getElementById('ampp-folder-display'); + if (el) el.textContent = selectedAmppFolderName || '(none — skip placement)'; +} + // ============================================================ // FILE HANDLING // ============================================================ @@ -1223,6 +1312,7 @@ async function uploadFilePresigned(item, idx) { setFileStatus(idx, 'uploading', 'Getting URL\u2026'); const pre = await api('POST', '/api/presigned', { filename: item.name, prefix: selectedPrefix, contentType: mime, size: item.file.size, + amppFolderId: selectedAmppFolderId, amppFolderName: selectedAmppFolderName, }); if (!pre.success) throw new Error(pre.error || 'Failed to get presigned URL'); return new Promise((resolve, reject) => { @@ -1237,7 +1327,7 @@ async function uploadFilePresigned(item, idx) { }); xhr.addEventListener('load', async () => { if (xhr.status >= 200 && xhr.status < 300) { - try { await api('POST', '/api/presigned/complete', { key: pre.key, size: item.file.size }); } catch(_) {} + try { await api('POST', '/api/presigned/complete', { key: pre.key, size: item.file.size, filename: item.name, amppFolderId: selectedAmppFolderId, amppFolderName: selectedAmppFolderName }); } catch(_) {} resolve({ key: pre.key }); } else { reject(new Error(`S3 returned ${xhr.status}: ${xhr.statusText}`)); @@ -1311,8 +1401,8 @@ async function uploadFilePresigned(item, idx) { if (pb) pb.style.width = '100%'; setFileStatus(idx, 'uploading', '100%'); - // Notify server for quota tracking - try { await api('POST', '/api/presigned/complete', { key, size: item.file.size }); } catch(_) {} + // Notify server for quota tracking + queue AMPP placement + try { await api('POST', '/api/presigned/complete', { key, size: item.file.size, filename: item.name, amppFolderId: selectedAmppFolderId, amppFolderName: selectedAmppFolderName }); } catch(_) {} return { key }; }