<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Admin Interface - Tanishq Dubey Photography</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@100..900&display=swap" rel="stylesheet"> <link rel="icon" href="data:;base64,iVBORw0KGgo="> <style> body { margin: 0; padding: 0; font-family: 'Noto Sans Mono', monospace; background-color: #f0f0f0; } .container { max-width: 1200px; margin: 0 auto; padding: 2rem; } h1 { color: {{ accent_color }}; margin-bottom: 1.5rem; } .upload-form { background-color: #ffffff; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); margin-bottom: 2rem; } input[type="file"] { margin-right: 1rem; } input[type="submit"] { background-color: {{ accent_color }}; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; font-family: inherit; font-weight: bold; } input[type="submit"]:hover { background-color: {{ accent_color }}e0; /* Slightly darker version of the accent color */ } table { width: 100%; border-collapse: collapse; background-color: #ffffff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #e0e0e0; } th { background-color: #f5f5f5; font-weight: bold; color: #333; } tr:hover { background-color: #f9f9f9; } .thumbnail { max-width: 100px; max-height: 100px; object-fit: cover; } .flash-messages { margin-bottom: 1rem; } .flash-messages p { background-color: #4CAF50; color: white; padding: 0.5rem; border-radius: 4px; } .logout { text-align: right; margin-bottom: 1rem; } .logout a { color: {{ accent_color }}; text-decoration: none; } .logout a:hover { text-decoration: underline; } .editable { cursor: pointer; } .editable:hover { background-color: #f0f0f0; } .editing { background-color: #fff; border: 1px solid #ccc; padding: 2px; } .delete-btn { background-color: #ff4136; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; cursor: pointer; font-family: inherit; font-weight: bold; margin-left: 0.5rem; } .delete-btn:hover { background-color: #ff1a1a; } .config-section { margin: 2rem 0; padding: 1rem; background: #fff; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .config-editor fieldset { margin-bottom: 1.5rem; border: 1px solid #ddd; padding: 1rem; border-radius: 4px; } .config-editor legend { font-weight: bold; padding: 0 0.5rem; } .form-group { margin-bottom: 1rem; } .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 500; } .form-group input, .form-group textarea { width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; } .form-group textarea { font-family: monospace; } </style> </head> <body> <div class="container"> <div class="logout"> <a href="{{ url_for('admin_logout') }}">Logout</a> </div> <h1>Admin Interface</h1> {% with messages = get_flashed_messages() %} {% if messages %} <div class="flash-messages"> {% for message in messages %} <p>{{ message }}</p> {% endfor %} </div> {% endif %} {% endwith %} <div class="upload-form"> <h2>Upload New Image</h2> <form action="{{ url_for('admin_upload') }}" method="POST" enctype="multipart/form-data"> <input type="file" name="file" accept=".jpg,.jpeg,.png,.gif" required> <input type="submit" value="Upload"> </form> </div> <h2>Uploaded Images</h2> <table> <thead> <tr> <th>Thumbnail</th> <th>Filename</th> <th>Date Taken</th> <th>Focal Length</th> <th>Aperture</th> <th>Shutter Speed</th> <th>ISO</th> <th>Dimensions</th> <th>Actions</th> </tr> </thead> <tbody> {% for photo in photos %} <tr data-id="{{ photo.id }}"> <td><img src="{{ url_for('serve_thumbnail', filename=photo.thumbnail_filename) }}" alt="Thumbnail" class="thumbnail"></td> <td class="editable" data-field="input_filename">{{ photo.input_filename }}</td> <td class="editable" data-field="date_taken">{{ photo.date_taken.strftime('%Y-%m-%d %H:%M:%S') }}</td> <td class="editable" data-field="focal_length">{{ photo.focal_length }}</td> <td class="editable" data-field="aperture">{{ photo.aperture }}</td> <td class="editable" data-field="shutter_speed">{{ photo.shutter_speed }}</td> <td class="editable" data-field="iso">{{ photo.iso }}</td> <td>{{ photo.width }}x{{ photo.height }}</td> <td> <button class="save-btn">Save</button> <button class="delete-btn">Delete</button> </td> </tr> {% endfor %} </tbody> </table> <div class="config-section"> <h2>Configuration</h2> <div class="config-editor"> <form id="configForm"> <!-- Appearance --> <fieldset> <legend>Appearance</legend> <div class="form-group"> <label for="appearance.accent_color">Accent Color:</label> <input style="min-height: 2rem;" type="color" id="appearance.accent_color" name="appearance.accent_color" value="{{ config.appearance.accent_color }}"> </div> <div class="form-group"> <label for="appearance.site_title">Site Title:</label> <input type="text" id="appearance.site_title" name="appearance.site_title" value="{{ config.appearance.site_title }}"> </div> </fieldset> <!-- About Section --> <fieldset> <legend>About</legend> <div class="form-group"> <label for="about.name">Name:</label> <input type="text" id="about.name" name="about.name" value="{{ config.about.name }}"> </div> <div class="form-group"> <label for="about.location">Location:</label> <input type="text" id="about.location" name="about.location" value="{{ config.about.location }}"> </div> <div class="form-group"> <label for="about.profile_image">Profile Image:</label> <div style="display: flex; align-items: center; gap: 1rem;"> <img id="profile-preview" src="/static/profile.jpeg" alt="Profile" style="width: 100px; height: 100px; object-fit: cover; border-radius: 50%;"> <input type="file" class="profile-image-upload" accept="image/jpeg,image/png" style="flex: 1;"> </div> </div> <div class="form-group"> <label for="about.bio">Bio (Markdown):</label> <textarea id="about.bio" name="about.bio" rows="4">{{ config.about.bio }}</textarea> </div> </fieldset> <button type="submit" class="btn btn-primary">Save Configuration</button> </form> </div> <div class="config-history-link" style="margin-top: 1rem;"> <a href="{{ url_for('config_history') }}">View Configuration History</a> </div> </div> </div> <script nonce="{{ nonce }}"> function makeEditable(element) { const value = element.textContent; const input = document.createElement('input'); input.value = value; input.classList.add('editing'); element.textContent = ''; element.appendChild(input); input.focus(); input.addEventListener('blur', function() { element.textContent = this.value; element.classList.remove('editing'); }); } document.querySelectorAll('.editable').forEach(el => { el.addEventListener('click', function() { if (!this.classList.contains('editing')) { makeEditable(this); } }); }); function saveChanges(event) { const button = event.target; const row = button.closest('tr'); const photoId = row.dataset.id; const updatedData = {}; row.querySelectorAll('.editable').forEach(el => { updatedData[el.dataset.field] = el.textContent; }); fetch('/admin/update_photo/' + photoId, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(updatedData), }) .then(response => response.json()) .then(data => { if (data.success) { alert('Changes saved successfully!'); } else { alert('Error saving changes: ' + data.error); } }) .catch((error) => { console.error('Error:', error); alert('An error occurred while saving changes.'); }); } function deletePhoto(event) { if (confirm('Are you sure you want to delete this photo?')) { const button = event.target; const row = button.closest('tr'); const photoId = row.dataset.id; fetch('/admin/delete_photo/' + photoId, { method: 'POST', }) .then(response => response.json()) .then(data => { if (data.success) { row.remove(); alert('Photo deleted successfully!'); } else { alert('Error deleting photo: ' + data.error); } }) .catch((error) => { console.error('Error:', error); alert('An error occurred while deleting the photo.'); }); } } document.querySelectorAll('.delete-btn').forEach(button => { button.addEventListener('click', (event) => deletePhoto(event)); }); document.querySelectorAll('.save-btn').forEach(button => { button.addEventListener('click', (event) => saveChanges(event)); }); document.querySelectorAll('.profile-image-upload').forEach(input => { input.addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; const formData = new FormData(); formData.append('profile_image', file); try { const response = await fetch('/admin/upload_profile', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { document.getElementById('profile-preview').src = '/static/profile.jpeg?' + new Date().getTime(); } else { alert('Error uploading profile image: ' + result.error); } } catch (error) { alert('Error uploading profile image: ' + error); } }); }); document.getElementById('configForm').addEventListener('submit', async (e) => { e.preventDefault(); const formData = {}; const inputs = e.target.querySelectorAll('input:not([type="file"]), textarea'); inputs.forEach(input => { formData[input.name] = input.value; }); try { const response = await fetch('/admin/update_config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const result = await response.json(); if (result.success) { alert('Configuration saved successfully'); location.reload(); } else { alert('Error saving configuration: ' + result.error); } } catch (error) { alert('Error saving configuration: ' + error); } }); </script> </body> </html>