Implement server-side file caching with service principal auth
- Remove progress meter for faster perceived load times - Fix async getGraphToken() calls with await - Implement service principal (app-only) authentication for file caching - Pre-cache jobs (2242 jobs) at startup via Redis - Background file caching for first 300 jobs on continuous cycle - Cache refreshes every 5 minutes to keep data current - All caches served instantly from Redis for all users/devices - No user authentication required for backend caching - App token auto-refreshes every 55 minutes Changes: - backend/src/index.ts: Added service principal flow, startup cache warming, background file caching - frontend/public/index.html: Removed progress bar, fixed getGraphToken() awaits - .env.example: Added GRAPH_TOKEN documentation
This commit is contained in:
+17
-30
@@ -60,10 +60,6 @@
|
||||
</div>
|
||||
<h1 class="m-0 mb-3 text-center text-blue-600 text-[33px] font-bold">Job Info</h1>
|
||||
|
||||
<div class="w-full bg-blue-50 h-1.5 rounded overflow-hidden mb-3">
|
||||
<div id="progressBar" class="h-full w-0 bg-blue-600 transition-all duration-300 ease-out"></div>
|
||||
</div>
|
||||
|
||||
<div class="relative mb-2">
|
||||
<input id="searchBox" placeholder="Search jobs (just start typing or use voice)" class="w-full p-2.5 pr-12 rounded-lg border border-gray-300 text-lg placeholder:text-gray-400">
|
||||
<button id="voiceSearchBtn" class="absolute right-2 top-1/2 -translate-y-1/2 w-8 h-8 rounded-full flex items-center justify-center hover:bg-gray-100 transition-colors" title="Voice search">
|
||||
@@ -289,7 +285,6 @@
|
||||
}
|
||||
window.clearJobsCache = clearJobsCache;
|
||||
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const searchBox = document.getElementById('searchBox');
|
||||
const resultsEl = document.getElementById('results');
|
||||
const filtersPanel = document.getElementById('filtersPanel');
|
||||
@@ -341,19 +336,8 @@
|
||||
}
|
||||
loadVersion();
|
||||
|
||||
let progTimer=null;
|
||||
function startProgress(){
|
||||
if(progTimer) clearInterval(progTimer);
|
||||
let simulatedProg=0;
|
||||
progTimer=setInterval(()=>{
|
||||
if(simulatedProg<65){ simulatedProg+=0.8; progressBar.style.width=Math.min(65,simulatedProg).toFixed(1)+'%'; }
|
||||
},150);
|
||||
}
|
||||
function finishProgress(){ if(progTimer) clearInterval(progTimer); progressBar.style.width='100%'; setTimeout(()=>{progressBar.parentElement.classList.add('hidden');},400); }
|
||||
|
||||
// --- Fetch all jobs ---
|
||||
async function fetchAllJobs(){
|
||||
startProgress();
|
||||
clearJobsCache('Loading…');
|
||||
const startTime = performance.now();
|
||||
let pollCount = 0;
|
||||
@@ -367,20 +351,17 @@
|
||||
if (pollCount === 0 || jobsCache.length === 0) {
|
||||
jobsCache.length = 0;
|
||||
jobsCache.push(...items);
|
||||
progressBar.style.width='95%';
|
||||
applyFiltersAndRender();
|
||||
}
|
||||
if (data.partial) {
|
||||
// If partial, poll again after short delay
|
||||
pollCount++;
|
||||
if (pollCount < 10) setTimeout(pollJobs, 600);
|
||||
else finishProgress();
|
||||
} else {
|
||||
finishProgress();
|
||||
const loadTime = Math.round(performance.now() - startTime);
|
||||
console.log(`✓ Loaded ${items.length} jobs from API in ${loadTime}ms`);
|
||||
}
|
||||
}catch(err){ finishProgress(); alert('Failed to fetch jobs: '+err.message); console.error(err); }
|
||||
}catch(err){ alert('Failed to fetch jobs: '+err.message); console.error(err); }
|
||||
}
|
||||
pollJobs();
|
||||
}
|
||||
@@ -1567,7 +1548,7 @@
|
||||
async function loadFilesForNotes(folderLink) {
|
||||
console.log('loadFilesForNotes called with:', folderLink);
|
||||
|
||||
const graphToken = getGraphToken();
|
||||
const graphToken = await getGraphToken();
|
||||
const headers = graphToken ? { 'x-graph-token': graphToken } : {};
|
||||
|
||||
try {
|
||||
@@ -2222,15 +2203,18 @@
|
||||
return 'other';
|
||||
};
|
||||
|
||||
// File cache for preloading
|
||||
// File cache for client-side only (server handles pre-caching)
|
||||
const fileCache = new Map();
|
||||
|
||||
async function preloadJobFiles(job) {
|
||||
const link = job.Job_Folder_Link;
|
||||
if (!link || fileCache.has(link)) return; // Skip if no link or already cached
|
||||
|
||||
if (!link) return;
|
||||
|
||||
// Simple client-side cache check
|
||||
if (fileCache.has(link)) return;
|
||||
|
||||
try {
|
||||
const graphToken = getGraphToken();
|
||||
const graphToken = await getGraphToken();
|
||||
const headers = graphToken ? { 'x-graph-token': graphToken } : {};
|
||||
|
||||
const controller = new AbortController();
|
||||
@@ -2246,11 +2230,14 @@
|
||||
...f,
|
||||
driveId: f.driveId || rootDriveId
|
||||
}));
|
||||
fileCache.set(link, { items, driveId: rootDriveId });
|
||||
console.log(`Preloaded ${items.length} files for job ${job.Job_Number}`);
|
||||
fileCache.set(link, { items, driveId: rootDriveId, cachedAt: Date.now() });
|
||||
console.log(`✓ Cached ${items.length} files for job ${job.Job_Number}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Preload failed (non-critical):', err);
|
||||
// Silently fail for background caching
|
||||
if (err.name !== 'AbortError') {
|
||||
console.debug(`Cache skip for job ${job.Job_Number}:`, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2515,7 +2502,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const graphToken = getGraphToken();
|
||||
const graphToken = await getGraphToken();
|
||||
if (!graphToken) {
|
||||
console.warn('Graph token missing; using fallback URL');
|
||||
openFileInViewer(fallbackUrl, name);
|
||||
@@ -2755,7 +2742,7 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
const graphToken = getGraphToken();
|
||||
const graphToken = await getGraphToken();
|
||||
const headers = graphToken ? { 'x-graph-token': graphToken } : {};
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user