From ce8c47d50d739674a1d535fc112a2f939a07acea Mon Sep 17 00:00:00 2001 From: brandon Date: Sat, 26 Jul 2025 19:00:14 -0700 Subject: [PATCH] admin panel and password protection --- admin.php | 101 +++++++++++++++++++++++++++++ index.html | 164 ++++++++++++++++++++++++++++++++++-------------- list-images.php | 57 +++++++++++++---- passwords.json | 1 + 4 files changed, 265 insertions(+), 58 deletions(-) create mode 100644 admin.php create mode 100644 passwords.json diff --git a/admin.php b/admin.php new file mode 100644 index 0000000..0115dd4 --- /dev/null +++ b/admin.php @@ -0,0 +1,101 @@ + + + + + + Admin - Album Passwords + + + +
+

Administrator Mode

+ +
+ + + + $error
"; ?> + + +
+ + + + + +
+ $success"; ?> +
+ +
+

Current Album Passwords:

+ + + + + diff --git a/index.html b/index.html index f54b225..2cbf316 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - Christiana Varner - Portland State University - Graduation 2025 + Album Viewer -

Christiana Varner - Portland State University - Graduation 2025

+ +

Album Viewer

+ Download All @@ -33,60 +35,130 @@ const gallery = document.getElementById('gallery'); const selectBtn = document.getElementById('select-images'); let downloadBtn = null; + let albums = {}; + + function getQueryParam(name) { + const urlParams = new URLSearchParams(window.location.search); + return urlParams.get(name); + } + + function renderGallery(images) { + gallery.innerHTML = ''; + images.forEach((imgObj, idx) => { + const itemDiv = document.createElement('div'); + itemDiv.style.position = 'relative'; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.className = 'img-checkbox'; + checkbox.style.position = 'absolute'; + checkbox.style.top = '8px'; + checkbox.style.left = '8px'; + checkbox.value = imgObj.full; + checkbox.style.display = 'none'; + + const link = document.createElement('a'); + link.href = imgObj.full; + link.target = '_blank'; + const img = document.createElement('img'); + img.src = imgObj.thumb; + img.alt = imgObj.full; + link.appendChild(img); + + itemDiv.addEventListener('click', function(e) { + if (checkbox.style.display === 'block') { + if (e.target === img || e.target === itemDiv) { + checkbox.checked = !checkbox.checked; + e.preventDefault(); + } + } + }); + + itemDiv.appendChild(checkbox); + itemDiv.appendChild(link); + gallery.appendChild(itemDiv); + }); + } fetch('list-images.php') .then(response => response.json()) - .then(images => { - images.forEach((imgObj, idx) => { - // Container for image and checkbox - const itemDiv = document.createElement('div'); - itemDiv.style.position = 'relative'; - - // Checkbox (hidden by default) - const checkbox = document.createElement('input'); - checkbox.type = 'checkbox'; - checkbox.className = 'img-checkbox'; - checkbox.style.position = 'absolute'; - checkbox.style.top = '8px'; - checkbox.style.left = '8px'; - checkbox.value = imgObj.full; - checkbox.style.display = 'none'; - - // Image link - const link = document.createElement('a'); - link.href = imgObj.full; - link.target = '_blank'; - const img = document.createElement('img'); - img.src = imgObj.thumb; - img.alt = imgObj.full; - link.appendChild(img); - - // Make image clickable for selection in select mode - itemDiv.addEventListener('click', function(e) { - // Only toggle if in select mode (checkboxes visible) - if (checkbox.style.display === 'block') { - // Prevent link from opening when selecting - if (e.target === img || e.target === itemDiv) { - checkbox.checked = !checkbox.checked; - e.preventDefault(); - } - } + .then(data => { + albums = data; + const albumName = getQueryParam('album'); + if (!albumName) { + // Landing page: show album links + gallery.innerHTML = '

Welcome!

Select an album to view photos:

'; + const albumList = document.createElement('div'); + albumList.style.display = 'flex'; + albumList.style.flexWrap = 'wrap'; + albumList.style.justifyContent = 'center'; + albumList.style.gap = '20px'; + Object.keys(albums).forEach(album => { + const link = document.createElement('a'); + link.href = `index.html?album=${encodeURIComponent(album)}`; + link.textContent = album + (albums[album].protected ? ' 🔒' : ''); + link.style.display = 'inline-block'; + link.style.padding = '16px 32px'; + link.style.background = '#0078d4'; + link.style.color = '#fff'; + link.style.borderRadius = '8px'; + link.style.fontWeight = 'bold'; + link.style.fontSize = '20px'; + link.style.textDecoration = 'none'; + link.style.boxShadow = '0 2px 8px rgba(0,0,0,0.12)'; + albumList.appendChild(link); + }); + gallery.appendChild(albumList); + selectBtn.style.display = 'none'; + return; + } + if (!albums[albumName]) { + gallery.innerHTML = '

Album not found. Please specify a valid album in the URL (e.g., ?album=Album1).

'; + selectBtn.style.display = 'none'; + return; + } + // Check if album is protected + if (albums[albumName].protected) { + let password = localStorage.getItem('album_pw_' + albumName) || ''; + function requestImages(pw) { + fetch(`list-images.php?album=${encodeURIComponent(albumName)}&password=${encodeURIComponent(pw)}`) + .then(resp => { + if (resp.status === 403) { + throw new Error('Password required'); + } + return resp.json(); + }) + .then(images => { + renderGallery(images); + selectBtn.style.display = 'inline-block'; + localStorage.setItem('album_pw_' + albumName, pw); + }) + .catch(() => { + gallery.innerHTML = `

Password Required

`; + selectBtn.style.display = 'none'; + document.getElementById('pwform').onsubmit = function(e) { + e.preventDefault(); + const pwTry = document.getElementById('album_pw').value; + requestImages(pwTry); + }; + }); + } + requestImages(password); + return; + } + // Not protected, fetch images + fetch(`list-images.php?album=${encodeURIComponent(albumName)}`) + .then(resp => resp.json()) + .then(images => { + renderGallery(images); + selectBtn.style.display = 'inline-block'; }); - itemDiv.appendChild(checkbox); - itemDiv.appendChild(link); - gallery.appendChild(itemDiv); - }); - - // Add select button logic selectBtn.addEventListener('click', () => { - // Show checkboxes document.querySelectorAll('.img-checkbox').forEach(cb => { cb.style.display = 'block'; }); - // Hide select button selectBtn.style.display = 'none'; - // Create and show download selected button downloadBtn = document.createElement('button'); downloadBtn.id = 'download-selected'; downloadBtn.textContent = 'Download Selected'; diff --git a/list-images.php b/list-images.php index 214b1f1..df264b2 100644 --- a/list-images.php +++ b/list-images.php @@ -1,24 +1,57 @@ 'Album not found']); + exit; + } + if (isset($passwords[$album]) && $passwords[$album] !== '') { + if ($pw !== $passwords[$album]) { + http_response_code(403); + echo json_encode(['error' => 'Password required']); + exit; + } + } + $albumImages = []; + $thumbDir = $dir . $album . '/thumbnails/'; + $albumDir = $dir . $album . '/'; + foreach (scandir($albumDir) as $file) { $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); if (in_array($ext, $extensions)) { - $thumbPath = 'images/thumbnails/' . $file; - $fullPath = 'images/' . $file; - // Check if thumbnail exists + $thumbPath = 'images/' . $album . '/thumbnails/' . $file; + $fullPath = 'images/' . $album . '/' . $file; if (file_exists($thumbDir . $file)) { - $images[] = ['thumb' => $thumbPath, 'full' => $fullPath]; + $albumImages[] = ['thumb' => $thumbPath, 'full' => $fullPath]; } else { - $images[] = ['thumb' => $fullPath, 'full' => $fullPath]; + $albumImages[] = ['thumb' => $fullPath, 'full' => $fullPath]; } } } + header('Content-Type: application/json'); + echo json_encode($albumImages); + exit; +} + +// Otherwise, return album list (for landing page) +if (is_dir($dir)) { + foreach (scandir($dir) as $album) { + if ($album === '.' || $album === '..' || !is_dir($dir . $album)) continue; + $albums[$album] = [ + 'protected' => isset($passwords[$album]) && $passwords[$album] !== '', + ]; + } } header('Content-Type: application/json'); -echo json_encode($images); +echo json_encode($albums); ?> \ No newline at end of file diff --git a/passwords.json b/passwords.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/passwords.json @@ -0,0 +1 @@ +{}