Fix AMPP picker: text input, path-based queuing, worker path resolution

This commit is contained in:
Claude 2026-04-30 21:49:16 -04:00
parent 1be810be24
commit e55555d2b7
3 changed files with 20 additions and 76 deletions

View file

@ -1,6 +1,6 @@
"use strict";
const { placeAssetInFolderById } = require("./ampp-folder-placer");
const { placeAssetInFolderById, getOrCreateFolderByPath } = require("./ampp-folder-placer");
// ================================================================
// AMPP Placement Worker — lib/ampp-placement-worker.js
@ -128,9 +128,14 @@ class AmppPlacementWorker {
continue;
}
console.log(`[placement-worker] Placing '${placement.filename}' → folder ${placement.amppFolderId} (asset ${assetId})`);
let targetFolderId = placement.amppFolderId;
if (!targetFolderId && placement.amppFolderName) {
try { targetFolderId = await getOrCreateFolderByPath(this._getAmppBase(), placement.amppFolderName, token); }
catch(e) { placement.status='failed'; placement.error=e.message; changed=true; continue; }
}
console.log(`[placement-worker] Placing '${placement.filename}' → folder ${targetFolderId} (asset ${assetId})`);
try {
await placeAssetInFolderById(this._getAmppBase(), assetId, placement.amppFolderId, token);
await placeAssetInFolderById(this._getAmppBase(), assetId, targetFolderId, token);
placement.status = "placed";
placement.assetId = assetId;
placement.placedAt = new Date().toISOString();

View file

@ -485,17 +485,17 @@ body::before{content:'';position:fixed;inset:0;background:radial-gradient(ellips
</div>
</div>
<!-- AMPP DESTINATION FOLDER -->
<div style="margin-bottom:1.5rem" id="ampp-folder-section">
<div class="section-title" style="display:flex;align-items:center;gap:.5rem">
AMPP Destination Folder
<span id="ampp-folder-status" style="font-size:.7rem;font-weight:400;text-transform:none;letter-spacing:0;color:var(--text-dim)">— optional</span>
<button id="ampp-folder-refresh-btn" onclick="loadAmppFolders()" style="margin-left:auto;padding:.2rem .55rem;font-size:.72rem;background:transparent;border:1px solid var(--border);border-radius:6px;color:var(--text-secondary);cursor:pointer">↻ Refresh</button>
<span style="font-size:.7rem;font-weight:400;text-transform:none;letter-spacing:0;color:var(--text-dim)">— optional</span>
</div>
<input class="form-input" id="ampp-folder-search" placeholder="🔍 Search AMPP folders…" oninput="renderAmppFolderList()" style="margin-bottom:.4rem;font-size:.78rem;padding:.4rem .65rem"/>
<div class="folder-tree-box" id="ampp-folder-box" style="max-height:220px;overflow-y:auto">
<div style="color:var(--text-dim);font-size:.8rem;padding:.75rem;text-align:center">Click ↻ Refresh to load AMPP folders</div>
</div>
<div style="margin-top:.45rem;font-size:.73rem;color:var(--text-dim)">
<input class="form-input" id="ampp-folder-path-input"
placeholder="e.g. NEWS/PACKAGES (leave blank to skip)"
oninput="updateAmppFolderFromInput(this.value)"
style="font-size:.82rem;margin-bottom:.35rem">
<div style="font-size:.75rem;color:var(--text-dim)">
AMPP folder: <code id="ampp-folder-display" style="color:var(--blue-bright);font-family:'JetBrains Mono',monospace">(none — skip placement)</code>
</div>
</div>
@ -879,7 +879,6 @@ function showApp() {
loadS3Config(); loadRelayConfig(); loadAmppConfig(); loadUsers(); loadShareLinks(); populateSlFolderSelect();
}
loadFolders();
loadAmppFolders();
loadAmppJobs();
}
@ -1087,70 +1086,10 @@ async function deleteFolder(pathArr) {
// ============================================================
// 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 = '<div style="color:var(--text-dim);font-size:.8rem;padding:.75rem;text-align:center">Loading AMPP folders…</div>';
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 = '<div style="color:var(--text-dim);font-size:.8rem;padding:.75rem;text-align:center">AMPP not configured — set up in Admin → AMPP</div>';
if (statusEl) statusEl.textContent = '— not configured';
} else {
box.innerHTML = `<div style="color:var(--red,#e55);font-size:.8rem;padding:.75rem;text-align:center">Error: ${esc(d.error||'Failed to load folders')}</div>`;
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 = `<div style="color:var(--red,#e55);font-size:.8rem;padding:.75rem;text-align:center">Error: ${esc(e.message)}</div>`;
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 = `<span class="ftr-icon">🚫</span><span class="ftr-name" style="font-style:italic;color:${selectedAmppFolderId===''?'':'var(--text-dim)'}">(no AMPP placement)</span>`;
noneRow.onclick = () => { selectedAmppFolderId = ''; selectedAmppFolderName = ''; updateAmppFolderDisplay(); renderAmppFolderList(); };
box.appendChild(noneRow);
if (!folders.length && amppFolderCache.length > 0) {
box.innerHTML += '<div style="color:var(--text-dim);font-size:.8rem;padding:.5rem .75rem">No folders match your search</div>';
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 = `<span class="ftr-icon">📁</span><span class="ftr-name">${esc(displayName)}</span>`;
row.onclick = () => { selectedAmppFolderId = id; selectedAmppFolderName = displayName; updateAmppFolderDisplay(); renderAmppFolderList(); };
box.appendChild(row);
});
}
function updateAmppFolderDisplay() {
const el = document.getElementById('ampp-folder-display');
function updateAmppFolderFromInput(val) {
selectedAmppFolderName = val.trim();
selectedAmppFolderId = '';
var el = document.getElementById('ampp-folder-display');
if (el) el.textContent = selectedAmppFolderName || '(none — skip placement)';
}

View file

@ -625,7 +625,7 @@ app.post("/api/presigned/complete", requireAuth, (req, res) => {
saveData(db);
}
// Record AMPP placement if a folder was selected
if (amppFolderId && filename) {
if ((amppFolderId || amppFolderName) && filename) {
if (!db.pendingPlacements) db.pendingPlacements = [];
db.pendingPlacements.push({
id: require("crypto").randomBytes(8).toString("hex"),