Checkpoint, configuration can now be done through DB and the site. Server settings are still on the file system
This commit is contained in:
@ -115,6 +115,42 @@
|
||||
.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>
|
||||
@ -173,8 +209,55 @@
|
||||
{% 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 Path:</label>
|
||||
<input type="text" id="about.profile_image" name="about.profile_image" value="{{ config.about.profile_image }}">
|
||||
</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>
|
||||
<script nonce="{{ g.csp_nonce }}">
|
||||
function makeEditable(element) {
|
||||
const value = element.textContent;
|
||||
const input = document.createElement('input');
|
||||
@ -250,6 +333,38 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('configForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = {};
|
||||
const inputs = e.target.querySelectorAll('input, 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>
|
||||
|
56
templates/config_history.html
Normal file
56
templates/config_history.html
Normal file
@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Site Configuration History</title>
|
||||
<style>
|
||||
.config-entry {
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.config-date {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
pre {
|
||||
background: #f5f5f5;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="logout">
|
||||
<a href="{{ url_for('admin') }}">Back to Admin</a> |
|
||||
<a href="{{ url_for('admin_logout') }}">Logout</a>
|
||||
</div>
|
||||
<h1>Site Configuration History</h1>
|
||||
|
||||
{% for config in site_configs %}
|
||||
<div class="config-entry">
|
||||
<div class="config-date">
|
||||
Updated: {{ config.updated_at.strftime('%Y-%m-%d %H:%M:%S UTC') }}
|
||||
</div>
|
||||
<pre>{
|
||||
"appearance": {
|
||||
"site_title": "{{ config.site_title }}",
|
||||
"accent_color": "{{ config.accent_color }}"
|
||||
},
|
||||
"about": {
|
||||
"name": "{{ config.author_name }}",
|
||||
"location": "{{ config.author_location }}",
|
||||
"profile_image": "{{ config.profile_image }}",
|
||||
"bio": {{ config.bio | tojson }}
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tanishq Dubey Photography</title>
|
||||
<title>{{ site_title }}</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">
|
||||
@ -172,31 +172,119 @@
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #f0f0f0;
|
||||
margin: 10% auto;
|
||||
padding: 20px;
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.profile-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.profile-info {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
font-size: 1.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.profile-location {
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-bio {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-nav noto-sans-mono-font">
|
||||
<div class="nav-toggle">☰</div>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><hr></li>
|
||||
<li>Powered by <a href="https://dws.rip">DWS</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<header>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-nav noto-sans-mono-font">
|
||||
<div class="nav-toggle">☰</div>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="#" id="aboutLink">About</a></li>
|
||||
<li><hr></li>
|
||||
<li>Powered by <a href="https://dws.rip">DWS</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="sidebar-title noto-sans-mono-font">
|
||||
<h1>{{ site_title }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-title noto-sans-mono-font">
|
||||
<h1>Tanishq Dubey Photography</h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="grid-container" id="polaroid-grid"></div>
|
||||
<div id="loading">Loading more images...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<div id="aboutModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<div class="modal-header">
|
||||
<img src="{{ about.profile_image }}" alt="{{ about.name }}" class="profile-image">
|
||||
<div class="profile-info">
|
||||
<h2 class="profile-name">{{ about.name }}</h2>
|
||||
<p class="profile-location">{{ about.location }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-bio" id="bio-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" nonce="{{ nonce }}"></script>
|
||||
|
||||
<script nonce="{{ nonce }}">
|
||||
const gridContainer = document.getElementById('polaroid-grid');
|
||||
const loadingIndicator = document.getElementById('loading');
|
||||
const baseSize = 110; // Size of one grid cell in pixels
|
||||
@ -345,6 +433,32 @@
|
||||
}
|
||||
|
||||
setupNavToggle();
|
||||
|
||||
// Modal functionality
|
||||
const modal = document.getElementById('aboutModal');
|
||||
const aboutLink = document.getElementById('aboutLink');
|
||||
const closeBtn = document.querySelector('.close');
|
||||
const bioContent = document.getElementById('bio-content');
|
||||
|
||||
// Render markdown content
|
||||
bioContent.innerHTML = marked.parse(`{{ about.bio | safe }}`);
|
||||
|
||||
aboutLink.onclick = function() {
|
||||
modal.style.display = 'block';
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
closeBtn.onclick = function() {
|
||||
modal.style.display = 'none';
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal) {
|
||||
modal.style.display = 'none';
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Reference in New Issue
Block a user