fix: grant heatmap API key ['all'] permissions + add Reset button

A narrow (asset.read, asset.view) key was insufficient for the
heatmap's /api/search/metadata call — the server returned an error.
Request ['all'] so the key covers every endpoint the heatmap touches.

Also add a Reset button next to the Collapse toggle so a stale or
under-permissioned cached key can be regenerated without site-data
surgery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-17 12:52:41 -04:00
parent 96d9a6971c
commit e55703ea6e
1 changed files with 24 additions and 9 deletions

View File

@ -961,9 +961,13 @@
// Get (or create) an Immich API key for the heatmap iframe so the user
// isn't prompted to paste one. Cached in this origin's localStorage.
async function getHeatmapApiKey() {
const cached = localStorage.getItem('ls-heatmap-api-key');
if (cached) return cached;
// Uses the ['all'] permission so the heatmap's /api/search/metadata and
// thumbnail fetches never 403.
async function getHeatmapApiKey(force) {
if (!force) {
const cached = localStorage.getItem('ls-heatmap-api-key');
if (cached) return cached;
}
try {
const r = await fetch('/api/api-keys', {
method: 'POST',
@ -971,11 +975,11 @@
credentials: 'include',
body: JSON.stringify({
name: 'Heatmap iframe (auto)',
permissions: ['asset.read', 'asset.view']
permissions: ['all']
})
});
if (!r.ok) {
console.warn('[live-search] api-key create failed', r.status);
console.warn('[live-search] api-key create failed', r.status, await r.text().catch(() => ''));
return null;
}
const j = await r.json();
@ -997,17 +1001,28 @@
banner.innerHTML = `
<div class="ls-heatmap-header">
<span>📍 Photo Locations Heatmap</span>
<button type="button">Collapse</button>
<div style="display:flex;gap:6px;">
<button type="button" data-act="reset" title="Regenerate API key">Reset</button>
<button type="button" data-act="toggle">Collapse</button>
</div>
</div>
<iframe class="ls-heatmap-frame"
referrerpolicy="no-referrer"
allow="geolocation"></iframe>
`;
const iframe = banner.querySelector('iframe');
const btn = banner.querySelector('button');
btn.addEventListener('click', () => {
const toggleBtn = banner.querySelector('button[data-act="toggle"]');
const resetBtn = banner.querySelector('button[data-act="reset"]');
toggleBtn.addEventListener('click', () => {
banner.classList.toggle('collapsed');
btn.textContent = banner.classList.contains('collapsed') ? 'Expand' : 'Collapse';
toggleBtn.textContent = banner.classList.contains('collapsed') ? 'Expand' : 'Collapse';
});
resetBtn.addEventListener('click', async () => {
localStorage.removeItem('ls-heatmap-api-key');
resetBtn.textContent = 'Resetting…';
const k = await getHeatmapApiKey(true);
iframe.src = HEATMAP_URL + (k ? '?apiKey=' + encodeURIComponent(k) + '&t=' + Date.now() : '');
resetBtn.textContent = 'Reset';
});
// Append at the END so it lands below People + Places sections.
container.appendChild(banner);