feat: add admin controls to manage and delete placed camera pins while protecting municipal portals

This commit is contained in:
2026-09-12 09:14:58 -07:00
parent b10b6e0f2f
commit 872be94476
3 changed files with 222 additions and 3 deletions
+36
View File
@@ -221,6 +221,11 @@
<span>Place Camera 400ft Ahead</span>
</button>
<button id="btn-admin-manage-pins" class="btn-hud-action" title="Admin: Manage and remove cameras you placed (the 1,439 official municipal portals are protected)">
<span>🗑️</span>
<span>Admin: Placed Pins</span>
</button>
<button id="btn-test-alert" class="btn-hud-action" title="Immediately test voice alert and radar siren">
<span>🔊</span>
<span>Test Alert Voice</span>
@@ -687,6 +692,26 @@
Continue as Guest Scout
</button>
</div>
<!-- Admin Camera Management Section -->
<div class="admin-profile-section" style="margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--border-subtle);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h4 style="font-size: 0.9rem; color: #f8fafc; display: flex; align-items: center; gap: 6px;">
<span>🛡️</span>
<span>Admin: Manage Placed Cameras</span>
</h4>
<span id="admin-placed-count" style="font-size: 0.75rem; color: #38bdf8; background: rgba(56, 189, 248, 0.15); padding: 2px 8px; border-radius: 10px;">0 Placed</span>
</div>
<p style="font-size: 0.76rem; color: #94a3b8; margin-bottom: 12px;">
Manage and remove any cameras you placed or tested. The 1,439 official municipal transparency portals remain permanent and protected.
</p>
<div id="admin-placed-cams-list" style="max-height: 160px; overflow-y: auto; margin-bottom: 12px; display: flex; flex-direction: column; gap: 6px;">
<!-- Rendered via JS -->
</div>
<button type="button" id="btn-admin-clear-all-placed" class="btn-secondary" style="width: 100%; padding: 10px; color: #f87171; border-color: rgba(239, 68, 68, 0.4); justify-content: center;">
🗑️ Clear All Placed Pins (Keep Real Locations)
</button>
</div>
</form>
</div>
</dialog>
@@ -839,6 +864,17 @@
Inspect Official Transparency Portal & Policy Records ↗
</button>
</div>
<!-- Admin Removal Control (Only Active for Custom Placed Pins) -->
<div id="cam-admin-control-box" class="admin-control-box" style="margin-top: 16px; padding-top: 14px; border-top: 1px dashed rgba(239, 68, 68, 0.3); text-align: center; display: none;">
<button type="button" id="btn-admin-delete-cam" class="btn-admin-delete" style="width: 100%; padding: 11px; background: rgba(239, 68, 68, 0.15); border: 1px solid #ef4444; color: #f87171; border-radius: var(--radius-md); font-weight: 600; font-size: 0.82rem; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; transition: all 0.2s ease;">
<span>🗑️</span>
<span>Admin: Delete This Placed Camera</span>
</button>
<span style="display: block; font-size: 0.7rem; color: #94a3b8; margin-top: 5px;">
(Only available for custom placed pins. The 1,439 official municipal portals are permanent & protected.)
</span>
</div>
</div>
</dialog>
+19 -2
View File
@@ -155,8 +155,24 @@
function removeReport(reportId) {
communityReports = communityReports.filter(r => r.id !== reportId);
if (cameraMetadata[reportId]) delete cameraMetadata[reportId];
saveReports();
saveMetadata();
localStorage.setItem(STORAGE_KEY_REPORTS, JSON.stringify(communityReports));
localStorage.setItem(STORAGE_KEY_METADATA, JSON.stringify(cameraMetadata));
window.dispatchEvent(new CustomEvent('flock_reports_updated', { detail: communityReports }));
return true;
}
function clearAllPlacedReports() {
communityReports = [];
localStorage.setItem(STORAGE_KEY_REPORTS, JSON.stringify([]));
// Clean up metadata for custom placed pins
Object.keys(cameraMetadata).forEach(id => {
if (id.startsWith('rpt-') || id.startsWith('usr-pin-') || id.startsWith('test-') || id.startsWith('cam-test-')) {
delete cameraMetadata[id];
}
});
localStorage.setItem(STORAGE_KEY_METADATA, JSON.stringify(cameraMetadata));
window.dispatchEvent(new CustomEvent('flock_reports_updated', { detail: [] }));
return true;
}
function saveReports() {
@@ -369,6 +385,7 @@
getReports,
addReport,
removeReport,
clearAllPlacedReports,
clearUnverifiedReports,
getCameraMeta,
confirmCamera,
+167 -1
View File
@@ -198,7 +198,15 @@
btnReportRemovePhoto: document.getElementById('btn-report-remove-photo'),
// Native Camera / File Input
cameraPhotoFileInput: document.getElementById('camera-photo-file-input')
cameraPhotoFileInput: document.getElementById('camera-photo-file-input'),
// Admin Placed Camera Controls
camAdminControlBox: document.getElementById('cam-admin-control-box'),
btnAdminDeleteCam: document.getElementById('btn-admin-delete-cam'),
adminPlacedCount: document.getElementById('admin-placed-count'),
adminPlacedCamsList: document.getElementById('admin-placed-cams-list'),
btnAdminClearAllPlaced: document.getElementById('btn-admin-clear-all-placed'),
btnAdminManagePins: document.getElementById('btn-admin-manage-pins')
};
// --- INITIALIZATION ---
@@ -410,6 +418,11 @@
Inspect Agency Records & Sharing ↗
</button>
` : ''}
${(cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-')) ? `
<button class="popup-inspect-btn" style="background: rgba(239, 68, 68, 0.18); border: 1px solid #ef4444; color: #f87171;" onclick="window.adminDeleteCamera('${escapeHtml(cam.id)}')">
🗑️ Admin: Delete Placed Pin
</button>
` : ''}
</div>
</div>
`;
@@ -1250,6 +1263,8 @@
document.getElementById('login-vehicle').value = user.vehicle;
}
renderAdminPlacedList();
if (typeof el.loginModal.showModal === 'function') {
el.loginModal.showModal();
} else {
@@ -1384,6 +1399,12 @@
}
}
// Admin Delete Control: ONLY visible for custom placed pins, NEVER for official scraped municipal portals
const isPlaced = cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-');
if (el.camAdminControlBox) {
el.camAdminControlBox.style.display = isPlaced ? 'block' : 'none';
}
// Show modal
if (el.cameraDetailModal) {
if (typeof el.cameraDetailModal.showModal === 'function') {
@@ -1404,6 +1425,107 @@
}
}
// --- ADMIN PLACED CAMERA MANAGEMENT ---
function renderAdminPlacedList() {
if (!el.adminPlacedCamsList) return;
const reports = window.FlockAuth ? window.FlockAuth.getReports() : [];
// Find all placed cameras from active radar memory (including test cameras)
const placedCams = allCameras.filter(c =>
c.isCommunity ||
c.id.startsWith('rpt-') ||
c.id.startsWith('usr-pin-') ||
c.id.startsWith('cam-test-') ||
c.id.startsWith('test-')
);
// Combine into unique map
const combinedMap = new Map();
reports.forEach(r => {
combinedMap.set(r.id, {
id: r.id,
name: r.intersection || r.type || 'Placed Camera',
lat: r.lat,
lng: r.lng,
dateStr: new Date(r.timestamp || Date.now()).toLocaleDateString(),
photoUrl: r.photoUrl || null
});
});
placedCams.forEach(c => {
if (!combinedMap.has(c.id)) {
combinedMap.set(c.id, {
id: c.id,
name: c.name || 'Test Placed Camera',
lat: c.lat,
lng: c.lng,
dateStr: 'Active Session',
photoUrl: null
});
}
});
const items = Array.from(combinedMap.values());
if (el.adminPlacedCount) el.adminPlacedCount.textContent = `${items.length} Placed`;
if (items.length === 0) {
el.adminPlacedCamsList.innerHTML = `
<div style="padding: 12px; text-align: center; color: #94a3b8; font-size: 0.8rem; background: rgba(30,41,59,0.4); border-radius: 6px;">
No custom placed cameras. All active radar cameras are official municipal portals.
</div>
`;
if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'none';
return;
}
if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'block';
el.adminPlacedCamsList.innerHTML = items.map(r => `
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; background: rgba(30,41,59,0.5); border: 1px solid rgba(148,163,184,0.1); border-radius: 6px; font-size: 0.78rem;">
<div style="display: flex; align-items: center; gap: 8px; overflow: hidden; max-width: 240px;">
${r.photoUrl ? `
<img src="${r.photoUrl}" alt="Thumbnail" style="width: 34px; height: 34px; object-fit: cover; border-radius: 4px; border: 1px solid var(--accent-cyan); flex-shrink: 0;">
` : `
<span style="font-size: 1.1rem; flex-shrink: 0;">📍</span>
`}
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<strong style="color: #f8fafc; display: block; overflow: hidden; text-overflow: ellipsis;">${escapeHtml(r.name)}</strong>
<span style="color: #94a3b8; font-size: 0.7rem;">${r.lat.toFixed(4)}, ${r.lng.toFixed(4)}${r.dateStr}</span>
</div>
</div>
<button type="button" class="btn-secondary" style="padding: 5px 10px; font-size: 0.74rem; color: #f87171; border-color: rgba(239,68,68,0.3); cursor: pointer; flex-shrink: 0;" onclick="window.adminDeleteCamera('${escapeHtml(r.id)}')">
🗑️ Delete
</button>
</div>
`).join('');
}
window.adminDeleteCamera = function(camId) {
// SECURITY GUARD: Protect official scraped municipal transparency portals
if (camId.startsWith('portal-') || (!camId.startsWith('rpt-') && !camId.startsWith('usr-pin-') && !camId.startsWith('test-') && !camId.startsWith('cam-test-'))) {
const targetCam = allCameras.find(c => c.id === camId);
if (targetCam && !targetCam.isCommunity) {
alert("Protected Municipal Portal: Official city and police department transparency records cannot be deleted. Only custom placed cameras can be removed.");
return;
}
}
const cam = allCameras.find(c => c.id === camId) || { name: 'Placed Camera', id: camId };
if (!confirm(`Admin Action: Permanently delete placed camera "${cam.name || cam.id}" from radar?`)) {
return;
}
if (window.FlockAuth) {
window.FlockAuth.removeReport(camId);
}
allCameras = allCameras.filter(c => c.id !== camId);
renderCameraZones();
renderAdminPlacedList();
closeCameraDetailModal();
playLogConfirmationChirp();
speakVoiceAlert("Placed camera deleted from radar.");
showToast("🗑️ Placed camera permanently removed.");
};
// --- CONTEST CAMERA MODAL ---
function openContestModal(cam) {
activeModalCamera = cam;
@@ -1615,6 +1737,14 @@
});
}
// Admin Delete Camera Button (Detail Modal)
if (el.btnAdminDeleteCam) {
el.btnAdminDeleteCam.addEventListener('click', () => {
if (!activeModalCamera) return;
window.adminDeleteCamera(activeModalCamera.id);
});
}
// Contest Modal Events
if (el.contestCloseBtn) el.contestCloseBtn.addEventListener('click', closeContestModal);
if (el.btnContestAddPhoto) el.btnContestAddPhoto.addEventListener('click', triggerPhotoCaptureForContest);
@@ -1723,6 +1853,42 @@
closeLoginModal();
});
// Admin Clear All Placed Pins Button (Keeps 1,439 real municipal portals)
if (el.btnAdminClearAllPlaced) {
el.btnAdminClearAllPlaced.addEventListener('click', () => {
if (!confirm("Admin Action: Clear ALL custom placed pins and radar test markers? (The 1,439 official municipal portals will remain permanent and protected).")) {
return;
}
if (window.FlockAuth) {
window.FlockAuth.clearAllPlacedReports();
}
allCameras = allCameras.filter(c => !c.isCommunity && !c.id.startsWith('rpt-') && !c.id.startsWith('usr-pin-') && !c.id.startsWith('test-') && !c.id.startsWith('cam-test-'));
renderCameraZones();
renderAdminPlacedList();
playLogConfirmationChirp();
speakVoiceAlert("All custom placed pins cleared from radar.");
showToast("🗑️ All custom placed pins cleared.");
});
}
// Admin Placed Pins Shortcut in Map Top Controls
if (el.btnAdminManagePins) {
el.btnAdminManagePins.addEventListener('click', () => {
openLoginModal();
setTimeout(() => {
const adminSec = document.querySelector('.admin-profile-section');
if (adminSec) adminSec.scrollIntoView({ behavior: 'smooth' });
}, 120);
});
}
// Sync radar when reports updated
window.addEventListener('flock_reports_updated', () => {
loadAllCameraData();
renderCameraZones();
renderAdminPlacedList();
});
// Report Form Submit
el.reportForm.addEventListener('submit', e => {
e.preventDefault();