diff --git a/index.html b/index.html
index 2ebcaa5..2777553 100644
--- a/index.html
+++ b/index.html
@@ -221,6 +221,11 @@
Place Camera 400ft Ahead
+
+
+
+
+
+
+
+ 🛡️
+ Admin: Manage Placed Cameras
+
+ 0 Placed
+
+
+ Manage and remove any cameras you placed or tested. The 1,439 official municipal transparency portals remain permanent and protected.
+
+
+
+
+
+
@@ -839,6 +864,17 @@
Inspect Official Transparency Portal & Policy Records ↗
+
+
+
+
+
+ (Only available for custom placed pins. The 1,439 official municipal portals are permanent & protected.)
+
+
diff --git a/js/auth.js b/js/auth.js
index bfa42ec..7e001c0 100644
--- a/js/auth.js
+++ b/js/auth.js
@@ -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,
diff --git a/js/radar.js b/js/radar.js
index ee49c50..72c31c9 100644
--- a/js/radar.js
+++ b/js/radar.js
@@ -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 ↗
` : ''}
+ ${(cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-')) ? `
+
+ ` : ''}
`;
@@ -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 = `
+
+ No custom placed cameras. All active radar cameras are official municipal portals.
+
+ `;
+ if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'none';
+ return;
+ }
+
+ if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'block';
+
+ el.adminPlacedCamsList.innerHTML = items.map(r => `
+
+
+ ${r.photoUrl ? `
+

+ ` : `
+
📍
+ `}
+
+ ${escapeHtml(r.name)}
+ ${r.lat.toFixed(4)}, ${r.lng.toFixed(4)} • ${r.dateStr}
+
+
+
+
+ `).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();